Home | History | Annotate | Line # | Download | only in libopts
      1 /*	$NetBSD: configfile.c,v 1.11 2024/08/18 20:47:24 christos Exp $	*/
      2 
      3 /**
      4  * \file configfile.c
      5  *
      6  *  configuration/rc/ini file handling.
      7  *
      8  * @addtogroup autoopts
      9  * @{
     10  */
     11 /*
     12  *  This file is part of AutoOpts, a companion to AutoGen.
     13  *  AutoOpts is free software.
     14  *  AutoOpts is Copyright (C) 1992-2018 by Bruce Korb - all rights reserved
     15  *
     16  *  AutoOpts is available under any one of two licenses.  The license
     17  *  in use must be one of these two and the choice is under the control
     18  *  of the user of the license.
     19  *
     20  *   The GNU Lesser General Public License, version 3 or later
     21  *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
     22  *
     23  *   The Modified Berkeley Software Distribution License
     24  *      See the file "COPYING.mbsd"
     25  *
     26  *  These files have the following sha256 sums:
     27  *
     28  *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
     29  *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
     30  *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
     31  */
     32 
     33 /**
     34  *  Skip over some unknown attribute
     35  *  @param[in] txt   start of skpped text
     36  *  @returns   character after skipped text
     37  */
     38 inline static char const *
     39 skip_unkn(char const * txt)
     40 {
     41     txt = BRK_END_XML_TOKEN_CHARS(txt);
     42     return (*txt == NUL) ? NULL : txt;
     43 }
     44 
     45 /*=export_func  configFileLoad
     46  *
     47  * what:  parse a configuration file
     48  * arg:   + char const * + fname + the file to load +
     49  *
     50  * ret_type:  const tOptionValue *
     51  * ret_desc:  An allocated, compound value structure
     52  *
     53  * doc:
     54  *  This routine will load a named configuration file and parse the
     55  *  text as a hierarchically valued option.  The option descriptor
     56  *  created from an option definition file is not used via this interface.
     57  *  The returned value is "named" with the input file name and is of
     58  *  type "@code{OPARG_TYPE_HIERARCHY}".  It may be used in calls to
     59  *  @code{optionGetValue()}, @code{optionNextValue()} and
     60  *  @code{optionUnloadNested()}.
     61  *
     62  * err:
     63  *  If the file cannot be loaded or processed, @code{NULL} is returned and
     64  *  @var{errno} is set.  It may be set by a call to either @code{open(2)}
     65  *  @code{mmap(2)} or other file system calls, or it may be:
     66  *  @itemize @bullet
     67  *  @item
     68  *  @code{ENOENT} - the file was not found.
     69  *  @item
     70  *  @code{ENOMSG} - the file was empty.
     71  *  @item
     72  *  @code{EINVAL} - the file contents are invalid -- not properly formed.
     73  *  @item
     74  *  @code{ENOMEM} - not enough memory to allocate the needed structures.
     75  *  @end itemize
     76 =*/
     77 const tOptionValue *
     78 configFileLoad(char const * fname)
     79 {
     80     tmap_info_t    cfgfile;
     81     tOptionValue * res = NULL;
     82     tOptionLoadMode save_mode = option_load_mode;
     83 
     84     char * txt = text_mmap(fname, PROT_READ, MAP_PRIVATE, &cfgfile);
     85 
     86     if (TEXT_MMAP_FAILED_ADDR(txt))
     87         return NULL; /* errno is set */
     88 
     89     option_load_mode = OPTION_LOAD_COOKED;
     90     res = optionLoadNested(txt, fname, strlen(fname));
     91 
     92     if (res == NULL) {
     93         int err = errno;
     94         text_munmap(&cfgfile);
     95         errno = err;
     96     } else
     97         text_munmap(&cfgfile);
     98 
     99     option_load_mode = save_mode;
    100     return res;
    101 }
    102 
    103 
    104 /*=export_func  optionFindValue
    105  *
    106  * what:  find a hierarcicaly valued option instance
    107  * arg:   + const tOptDesc * + odesc + an option with a nested arg type +
    108  * arg:   + char const *     + name  + name of value to find +
    109  * arg:   + char const *     + val   + the matching value    +
    110  *
    111  * ret_type:  const tOptionValue *
    112  * ret_desc:  a compound value structure
    113  *
    114  * doc:
    115  *  This routine will find an entry in a nested value option or configurable.
    116  *  It will search through the list and return a matching entry.
    117  *
    118  * err:
    119  *  The returned result is NULL and errno is set:
    120  *  @itemize @bullet
    121  *  @item
    122  *  @code{EINVAL} - the @code{pOptValue} does not point to a valid
    123  *  hierarchical option value.
    124  *  @item
    125  *  @code{ENOENT} - no entry matched the given name.
    126  *  @end itemize
    127 =*/
    128 const tOptionValue *
    129 optionFindValue(const tOptDesc * odesc, char const * name, char const * val)
    130 {
    131     const tOptionValue * res = NULL;
    132 
    133     if (  (odesc == NULL)
    134        || (OPTST_GET_ARGTYPE(odesc->fOptState) != OPARG_TYPE_HIERARCHY))  {
    135         errno = EINVAL;
    136     }
    137 
    138     else if (odesc->optCookie == NULL) {
    139         errno = ENOENT;
    140     }
    141 
    142     else do {
    143         tArgList * argl  = odesc->optCookie;
    144         int        argct = argl->useCt;
    145         void **    poptv = __UNCONST(argl->apzArgs);
    146 
    147         if (argct == 0) {
    148             errno = ENOENT;
    149             break;
    150         }
    151 
    152         if (name == NULL) {
    153             res = (tOptionValue *)*poptv;
    154             break;
    155         }
    156 
    157         while (--argct >= 0) {
    158             const tOptionValue * ov = *(poptv++);
    159             const tOptionValue * rv = optionGetValue(ov, name);
    160 
    161             if (rv == NULL)
    162                 continue;
    163 
    164             if (val == NULL) {
    165                 res = ov;
    166                 break;
    167             }
    168         }
    169         if (res == NULL)
    170             errno = ENOENT;
    171     } while (false);
    172 
    173     return res;
    174 }
    175 
    176 
    177 /*=export_func  optionFindNextValue
    178  *
    179  * FIXME: the handling of 'pzName' and 'pzVal' is just wrong.
    180  *
    181  * what:  find a hierarcicaly valued option instance
    182  * arg:   + const tOptDesc * + odesc + an option with a nested arg type +
    183  * arg:   + const tOptionValue * + pPrevVal + the last entry +
    184  * arg:   + char const *     + name     + name of value to find +
    185  * arg:   + char const *     + value    + the matching value    +
    186  *
    187  * ret_type:  const tOptionValue *
    188  * ret_desc:  a compound value structure
    189  *
    190  * doc:
    191  *  This routine will find the next entry in a nested value option or
    192  *  configurable.  It will search through the list and return the next entry
    193  *  that matches the criteria.
    194  *
    195  * err:
    196  *  The returned result is NULL and errno is set:
    197  *  @itemize @bullet
    198  *  @item
    199  *  @code{EINVAL} - the @code{pOptValue} does not point to a valid
    200  *  hierarchical option value.
    201  *  @item
    202  *  @code{ENOENT} - no entry matched the given name.
    203  *  @end itemize
    204 =*/
    205 tOptionValue const *
    206 optionFindNextValue(const tOptDesc * odesc, const tOptionValue * pPrevVal,
    207                     char const * pzName, char const * pzVal)
    208 {
    209     bool old_found = false;
    210     tOptionValue * res = NULL;
    211 
    212     (void)pzName;
    213     (void)pzVal;
    214 
    215     if (  (odesc == NULL)
    216        || (OPTST_GET_ARGTYPE(odesc->fOptState) != OPARG_TYPE_HIERARCHY))  {
    217         errno = EINVAL;
    218     }
    219 
    220     else if (odesc->optCookie == NULL) {
    221         errno = ENOENT;
    222     }
    223 
    224     else do {
    225         tArgList * argl = odesc->optCookie;
    226         int        ct   = argl->useCt;
    227         void **   poptv = __UNCONST(argl->apzArgs);
    228 
    229         while (--ct >= 0) {
    230             tOptionValue * pOV = *(poptv++);
    231             if (old_found) {
    232                 res = pOV;
    233                 break;
    234             }
    235             if (pOV == pPrevVal)
    236                 old_found = true;
    237         }
    238         if (res == NULL)
    239             errno = ENOENT;
    240     } while (false);
    241 
    242     return res;
    243 }
    244 
    245 
    246 /*=export_func  optionGetValue
    247  *
    248  * what:  get a specific value from a hierarcical list
    249  * arg:   + const tOptionValue * + pOptValue + a hierarchcal value +
    250  * arg:   + char const *         + valueName + name of value to get +
    251  *
    252  * ret_type:  const tOptionValue *
    253  * ret_desc:  a compound value structure
    254  *
    255  * doc:
    256  *  This routine will find an entry in a nested value option or configurable.
    257  *  If "valueName" is NULL, then the first entry is returned.  Otherwise,
    258  *  the first entry with a name that exactly matches the argument will be
    259  *  returned.  If there is no matching value, NULL is returned and errno is
    260  *  set to ENOENT. If the provided option value is not a hierarchical value,
    261  *  NULL is also returned and errno is set to EINVAL.
    262  *
    263  * err:
    264  *  The returned result is NULL and errno is set:
    265  *  @itemize @bullet
    266  *  @item
    267  *  @code{EINVAL} - the @code{pOptValue} does not point to a valid
    268  *  hierarchical option value.
    269  *  @item
    270  *  @code{ENOENT} - no entry matched the given name.
    271  *  @end itemize
    272 =*/
    273 tOptionValue const *
    274 optionGetValue(tOptionValue const * oov, char const * vname)
    275 {
    276     tArgList *     arg_list;
    277     tOptionValue * res = NULL;
    278 
    279     if ((oov == NULL) || (oov->valType != OPARG_TYPE_HIERARCHY)) {
    280         errno = EINVAL;
    281         return res;
    282     }
    283     arg_list = oov->v.nestVal;
    284 
    285     if (arg_list->useCt > 0) {
    286         int     ct     = arg_list->useCt;
    287         void ** ovlist = __UNCONST(arg_list->apzArgs);
    288 
    289         if (vname == NULL) {
    290             res = (tOptionValue *)*ovlist;
    291 
    292         } else do {
    293             tOptionValue * opt_val = *(ovlist++);
    294             if (strcmp(opt_val->pzName, vname) == 0) {
    295                 res = opt_val;
    296                 break;
    297             }
    298         } while (--ct > 0);
    299     }
    300     if (res == NULL)
    301         errno = ENOENT;
    302     return res;
    303 }
    304 
    305 /*=export_func  optionNextValue
    306  *
    307  * what:  get the next value from a hierarchical list
    308  * arg:   + const tOptionValue * + pOptValue + a hierarchcal list value +
    309  * arg:   + const tOptionValue * + pOldValue + a value from this list   +
    310  *
    311  * ret_type:  const tOptionValue *
    312  * ret_desc:  a compound value structure
    313  *
    314  * doc:
    315  *  This routine will return the next entry after the entry passed in.  At the
    316  *  end of the list, NULL will be returned.  If the entry is not found on the
    317  *  list, NULL will be returned and "@var{errno}" will be set to EINVAL.
    318  *  The "@var{pOldValue}" must have been gotten from a prior call to this
    319  *  routine or to "@code{opitonGetValue()}".
    320  *
    321  * err:
    322  *  The returned result is NULL and errno is set:
    323  *  @itemize @bullet
    324  *  @item
    325  *  @code{EINVAL} - the @code{pOptValue} does not point to a valid
    326  *  hierarchical option value or @code{pOldValue} does not point to a
    327  *  member of that option value.
    328  *  @item
    329  *  @code{ENOENT} - the supplied @code{pOldValue} pointed to the last entry.
    330  *  @end itemize
    331 =*/
    332 tOptionValue const *
    333 optionNextValue(tOptionValue const * ov_list,tOptionValue const * oov )
    334 {
    335     tArgList *     arg_list;
    336     tOptionValue * res = NULL;
    337     int            err = EINVAL;
    338 
    339     if ((ov_list == NULL) || (ov_list->valType != OPARG_TYPE_HIERARCHY)) {
    340         errno = EINVAL;
    341         return NULL;
    342     }
    343     arg_list = ov_list->v.nestVal;
    344     {
    345         int     ct    = arg_list->useCt;
    346         void ** o_list = __UNCONST(arg_list->apzArgs);
    347 
    348         while (ct-- > 0) {
    349             tOptionValue * nov = *(o_list++);
    350             if (nov == oov) {
    351                 if (ct == 0) {
    352                     err = ENOENT;
    353 
    354                 } else {
    355                     err = 0;
    356                     res = (tOptionValue *)*o_list;
    357                 }
    358                 break;
    359             }
    360         }
    361     }
    362     if (err != 0)
    363         errno = err;
    364     return res;
    365 }
    366 
    367 /**
    368  *  Load a file containing presetting information (a configuration file).
    369  */
    370 static void
    371 file_preset(tOptions * opts, char const * fname, int dir)
    372 {
    373     tmap_info_t       cfgfile;
    374     tOptState         optst = OPTSTATE_INITIALIZER(PRESET);
    375     opt_state_mask_t  st_flags = optst.flags;
    376     opt_state_mask_t  fl_save  = opts->fOptSet;
    377     char *            ftext =
    378         text_mmap(fname, PROT_READ|PROT_WRITE, MAP_PRIVATE, &cfgfile);
    379 
    380     if (TEXT_MMAP_FAILED_ADDR(ftext))
    381         return;
    382 
    383     /*
    384      * While processing config files, we ignore errors.
    385      */
    386     opts->fOptSet &= ~OPTPROC_ERRSTOP;
    387 
    388     if (dir == DIRECTION_CALLED) {
    389         st_flags = OPTST_DEFINED;
    390         dir   = DIRECTION_PROCESS;
    391     }
    392 
    393     /*
    394      *  IF this is called via "optionProcess", then we are presetting.
    395      *  This is the default and the PRESETTING bit will be set.
    396      *  If this is called via "optionFileLoad", then the bit is not set
    397      *  and we consider stuff set herein to be "set" by the client program.
    398      */
    399     if ((opts->fOptSet & OPTPROC_PRESETTING) == 0)
    400         st_flags = OPTST_SET;
    401 
    402     do  {
    403         optst.flags = st_flags;
    404         ftext = SPN_WHITESPACE_CHARS(ftext);
    405 
    406         if (IS_VAR_FIRST_CHAR(*ftext)) {
    407             ftext = handle_cfg(opts, &optst, ftext, dir);
    408 
    409         } else switch (*ftext) {
    410         case '<':
    411             if (IS_VAR_FIRST_CHAR(ftext[1]))
    412                 ftext = handle_struct(opts, &optst, ftext, dir);
    413 
    414             else switch (ftext[1]) {
    415             case '?':
    416                 ftext = handle_directive(opts, ftext);
    417                 break;
    418 
    419             case '!':
    420                 ftext = handle_comment(ftext);
    421                 break;
    422 
    423             case '/':
    424                 ftext = strchr(ftext + 2, '>');
    425                 if (ftext++ != NULL)
    426                     break;
    427                 /* FALLTHROUGH */
    428 
    429             default:
    430                 ftext = NULL;
    431             }
    432             if (ftext == NULL)
    433                 goto all_done;
    434             break;
    435 
    436         case '[':
    437             ftext = handle_section(opts, ftext);
    438             break;
    439 
    440         case '#':
    441             ftext = strchr(ftext + 1, NL);
    442             break;
    443 
    444         default:
    445             goto all_done; /* invalid format */
    446         }
    447     } while (ftext != NULL);
    448 
    449  all_done:
    450     text_munmap(&cfgfile);
    451     opts->fOptSet = fl_save;
    452 }
    453 
    454 /**
    455  *  "txt" points to a "<!" sequence.
    456  *  Theoretically, we should ensure that it begins with "<!--",
    457  *  but actually I don't care that much.  It ends with "-->".
    458  */
    459 static char *
    460 handle_comment(char * txt)
    461 {
    462     char * pz = strstr(txt, "-->");
    463     if (pz != NULL)
    464         pz += 3;
    465     return pz;
    466 }
    467 
    468 /**
    469  *  "txt" points to the start of some value name.
    470  *  The end of the entry is the end of the line that is not preceded by
    471  *  a backslash escape character.  The string value is always processed
    472  *  in "cooked" mode.
    473  */
    474 static char *
    475 handle_cfg(tOptions * opts, tOptState * ost, char * txt, int dir)
    476 {
    477     char * pzName = txt++;
    478     char * pzEnd  = strchr(txt, NL);
    479 
    480     if (pzEnd == NULL)
    481         return txt + strlen(txt);
    482 
    483     txt = SPN_VALUE_NAME_CHARS(txt);
    484     txt = SPN_WHITESPACE_CHARS(txt);
    485     if (txt > pzEnd) {
    486     name_only:
    487         *pzEnd++ = NUL;
    488         load_opt_line(opts, ost, pzName, dir, OPTION_LOAD_UNCOOKED);
    489         return pzEnd;
    490     }
    491 
    492     /*
    493      *  Either the first character after the name is a ':' or '=',
    494      *  or else we must have skipped over white space.  Anything else
    495      *  is an invalid format and we give up parsing the text.
    496      */
    497     if ((*txt == '=') || (*txt == ':')) {
    498         txt = SPN_WHITESPACE_CHARS(txt+1);
    499         if (txt > pzEnd)
    500             goto name_only;
    501     } else if (! IS_WHITESPACE_CHAR(txt[-1]))
    502         return NULL;
    503 
    504     /*
    505      *  IF the value is continued, remove the backslash escape and push "pzEnd"
    506      *  on to a newline *not* preceded by a backslash.
    507      */
    508     if (pzEnd[-1] == '\\') {
    509         char * pcD = pzEnd-1;
    510         char * pcS = pzEnd;
    511 
    512         for (;;) {
    513             char ch = *(pcS++);
    514             switch (ch) {
    515             case NUL:
    516                 pcS = NULL;
    517                 /* FALLTHROUGH */
    518 
    519             case NL:
    520                 *pcD = NUL;
    521                 pzEnd = pcS;
    522                 goto copy_done;
    523 
    524             case '\\':
    525                 if (*pcS == NL)
    526                     ch = *(pcS++);
    527                 /* FALLTHROUGH */
    528             default:
    529                 *(pcD++) = ch;
    530             }
    531         } copy_done:;
    532 
    533     } else {
    534         /*
    535          *  The newline was not preceded by a backslash.  NUL it out
    536          */
    537         *(pzEnd++) = NUL;
    538     }
    539 
    540     /*
    541      *  "pzName" points to what looks like text for one option/configurable.
    542      *  It is NUL terminated.  Process it.
    543      */
    544     load_opt_line(opts, ost, pzName, dir, OPTION_LOAD_UNCOOKED);
    545 
    546     return pzEnd;
    547 }
    548 
    549 /**
    550  *  "txt" points to a "<?" sequence.
    551  *  We handle "<?program" and "<?auto-options" directives.
    552  *  All others are treated as comments.
    553  *
    554  *  @param[in,out] opts  program option descriptor
    555  *  @param[in]     txt   scanning pointer
    556  *  @returns       the next character to look at
    557  */
    558 static char *
    559 handle_directive(tOptions * opts, char * txt)
    560 {
    561 #   define DIRECTIVE_TABLE                      \
    562     _dt_(zCfgProg,     program_directive)       \
    563     _dt_(zCfgAO_Flags, aoflags_directive)
    564 
    565     typedef char * (directive_func_t)(tOptions *, char *);
    566 #   define _dt_(_s, _fn) _fn,
    567     static directive_func_t * dir_disp[] = {
    568         DIRECTIVE_TABLE
    569     };
    570 #   undef  _dt_
    571 
    572 #   define _dt_(_s, _fn) 1 +
    573     static int  const   dir_ct  = DIRECTIVE_TABLE 0;
    574     static char const * dir_names[DIRECTIVE_TABLE 0];
    575 #   undef _dt_
    576 
    577     int    ix;
    578 
    579     if (dir_names[0] == NULL) {
    580         ix = 0;
    581 #   define _dt_(_s, _fn) dir_names[ix++] = _s;
    582         DIRECTIVE_TABLE;
    583 #   undef _dt_
    584     }
    585 
    586     for (ix = 0; ix < dir_ct; ix++) {
    587         size_t len = strlen(dir_names[ix]);
    588         if (  (strncmp(txt, dir_names[ix], len) == 0)
    589            && (! IS_VALUE_NAME_CHAR(txt[len])) )
    590             return dir_disp[ix](opts, txt + len);
    591     }
    592 
    593     /*
    594      *  We don't know what this is.  Skip it.
    595      */
    596     txt = strchr(txt+2, '>');
    597     if (txt != NULL)
    598         txt++;
    599     return txt;
    600 #   undef DIRECTIVE_TABLE
    601 }
    602 
    603 /**
    604  *  handle AutoOpts mode flags.
    605  *
    606  *  @param[in,out] opts  program option descriptor
    607  *  @param[in]     txt   scanning pointer
    608  *  @returns       the next character to look at
    609  */
    610 static char *
    611 aoflags_directive(tOptions * opts, char * txt)
    612 {
    613     char * pz;
    614 
    615     pz = SPN_WHITESPACE_CHARS(txt+1);
    616     txt = strchr(pz, '>');
    617     if (txt != NULL) {
    618 
    619         size_t len  = (unsigned)(txt - pz);
    620         char * ftxt = AGALOC(len + 1, "aoflags");
    621 
    622         memcpy(ftxt, pz, len);
    623         ftxt[len] = NUL;
    624         set_usage_flags(opts, ftxt);
    625         AGFREE(ftxt);
    626 
    627         txt++;
    628     }
    629 
    630     return txt;
    631 }
    632 
    633 /**
    634  * handle program segmentation of config file.
    635  *
    636  *  @param[in,out] opts  program option descriptor
    637  *  @param[in]     txt   scanning pointer
    638  *  @returns       the next character to look at
    639  */
    640 static char *
    641 program_directive(tOptions * opts, char * txt)
    642 {
    643     size_t name_len = strlen(opts->pzProgName);
    644 
    645     for (;; txt += zCfgProg_LEN) {
    646         txt = SPN_WHITESPACE_CHARS(txt);
    647 
    648         if (  (strneqvcmp(txt, opts->pzProgName, (int)name_len) == 0)
    649            && (IS_END_XML_TOKEN_CHAR(txt[name_len])) )
    650 
    651             return txt + name_len;
    652 
    653         txt = strstr(txt, zCfgProg);
    654         if (txt == NULL)
    655             return txt;
    656     }
    657 
    658     for (;;) {
    659         if (*txt == NUL)
    660             return NULL;
    661 
    662         if (*(txt++) == '>')
    663             return txt;
    664     }
    665 }
    666 
    667 /**
    668  *  "txt" points to a '[' character.
    669  *  The "traditional" [PROG_NAME] segmentation of the config file.
    670  *  Do not ever mix with the "<?program prog-name>" variation.
    671  *  The templates reject program names over 16 characters.
    672  *
    673  *  @param[in,out] opts  program option descriptor
    674  *  @param[in]     txt   scanning pointer
    675  *  @returns       the next character to look at
    676  */
    677 static char *
    678 handle_section(tOptions * opts, char * txt)
    679 {
    680     size_t len = strlen(opts->pzPROGNAME);
    681     if (   (strncmp(txt+1, opts->pzPROGNAME, len) == 0)
    682         && (txt[len+1] == ']'))
    683         return strchr(txt + len + 2, NL);
    684 
    685     if (len > 16)
    686         return NULL;
    687 
    688     {
    689         char z[24] = "[";
    690         memcpy(z+1, opts->pzPROGNAME, len);
    691         z[++len] = ']';
    692         z[++len] = NUL;
    693         txt = strstr(txt, z);
    694     }
    695 
    696     if (txt != NULL)
    697         txt = strchr(txt, NL);
    698     return txt;
    699 }
    700 
    701 /**
    702  * parse XML encodings
    703  */
    704 static int
    705 parse_xml_encoding(char ** ppz)
    706 {
    707 #   define XMLTABLE             \
    708         _xmlNm_(amp,   '&')     \
    709         _xmlNm_(lt,    '<')     \
    710         _xmlNm_(gt,    '>')     \
    711         _xmlNm_(ff,    '\f')    \
    712         _xmlNm_(ht,    '\t')    \
    713         _xmlNm_(cr,    '\r')    \
    714         _xmlNm_(vt,    '\v')    \
    715         _xmlNm_(bel,   '\a')    \
    716         _xmlNm_(nl,    NL)      \
    717         _xmlNm_(space, ' ')     \
    718         _xmlNm_(quot,  '"')     \
    719         _xmlNm_(apos,  '\'')
    720 
    721     static struct {
    722         char const * const  nm_str;
    723         unsigned short      nm_len;
    724         short               nm_val;
    725     } const xml_names[] = {
    726 #   define _xmlNm_(_n, _v) { #_n ";", sizeof(#_n), _v },
    727         XMLTABLE
    728 #   undef  _xmlNm_
    729 #   undef XMLTABLE
    730     };
    731 
    732     static int const nm_ct = sizeof(xml_names) / sizeof(xml_names[0]);
    733     int    base = 10;
    734 
    735     char * pz = *ppz;
    736 
    737     if (*pz == '#') {
    738         pz++;
    739         goto parse_number;
    740     }
    741 
    742     if (IS_DEC_DIGIT_CHAR(*pz)) {
    743         unsigned long v;
    744 
    745     parse_number:
    746         switch (*pz) {
    747         case 'x': case 'X':
    748             /*
    749              * Some forms specify hex with:  &#xNN;
    750              */
    751             base = 16;
    752             pz++;
    753             break;
    754 
    755         case '0':
    756             /*
    757              *  &#0022; is hex and &#22; is decimal.  Cool.
    758              *  Ya gotta love it.
    759              */
    760             if (pz[1] == '0')
    761                 base = 16;
    762             break;
    763         }
    764 
    765         v = strtoul(pz, &pz, base);
    766         if ((*pz != ';') || (v > 0x7F))
    767             return NUL;
    768         *ppz = pz + 1;
    769         return (int)v;
    770     }
    771 
    772     {
    773         int ix = 0;
    774         do  {
    775             if (strncmp(pz, xml_names[ix].nm_str, xml_names[ix].nm_len)
    776                 == 0) {
    777                 *ppz = pz + xml_names[ix].nm_len;
    778                 return xml_names[ix].nm_val;
    779             }
    780         } while (++ix < nm_ct);
    781     }
    782 
    783     return NUL;
    784 }
    785 
    786 /**
    787  * Find the end marker for the named section of XML.
    788  * Trim that text there, trimming trailing white space for all modes
    789  * except for OPTION_LOAD_UNCOOKED.
    790  */
    791 static char *
    792 trim_xml_text(char * intxt, char const * pznm, tOptionLoadMode mode)
    793 {
    794     size_t nm_len = strlen(pznm);
    795     char * etext;
    796 
    797     {
    798         char z[64], *pz = z;
    799 
    800         if (nm_len + 4 >= sizeof(z))
    801             pz = AGALOC(nm_len + 4, "scan name");
    802 
    803         pz[0] = '<';
    804         pz[1] = '/';
    805         memcpy(pz+2, pznm, nm_len);
    806         nm_len  += 2;
    807         pz[nm_len++] = '>';
    808         pz[nm_len]   = NUL;
    809 
    810         *intxt = ' ';
    811         etext = strstr(intxt, pz);
    812         if (pz != z) AGFREE(pz);
    813     }
    814 
    815     if (etext == NULL)
    816         return etext;
    817 
    818     {
    819         char * result = etext + nm_len;
    820 
    821         if (mode != OPTION_LOAD_UNCOOKED)
    822             etext = SPN_WHITESPACE_BACK(intxt, etext);
    823 
    824         *etext = NUL;
    825         return result;
    826     }
    827 }
    828 
    829 /**
    830  */
    831 static void
    832 cook_xml_text(char * pzData)
    833 {
    834     char * pzs = pzData;
    835     char * pzd = pzData;
    836     char   bf[4];
    837     bf[2] = NUL;
    838 
    839     for (;;) {
    840         int ch = ((int)*(pzs++)) & 0xFF;
    841         switch (ch) {
    842         case NUL:
    843             *pzd = NUL;
    844             return;
    845 
    846         case '&':
    847             ch = parse_xml_encoding(&pzs);
    848             *(pzd++) = (char)ch;
    849             if (ch == NUL)
    850                 return;
    851             break;
    852 
    853         case '%':
    854             bf[0] = *(pzs++);
    855             bf[1] = *(pzs++);
    856             if ((bf[0] == NUL) || (bf[1] == NUL)) {
    857                 *pzd = NUL;
    858                 return;
    859             }
    860 
    861             ch = (int)strtoul(bf, NULL, 16);
    862             /* FALLTHROUGH */
    863 
    864         default:
    865             *(pzd++) = (char)ch;
    866         }
    867     }
    868 }
    869 
    870 /**
    871  *  "txt" points to a '<' character, followed by an alpha.
    872  *  The end of the entry is either the "/>" following the name, or else a
    873  *  "</name>" string.
    874  */
    875 static char *
    876 handle_struct(tOptions * opts, tOptState * ost, char * txt, int dir)
    877 {
    878     tOptionLoadMode mode = option_load_mode;
    879     tOptionValue    valu;
    880 
    881     char * pzName = ++txt;
    882     char * pzData;
    883     char * pcNulPoint;
    884 
    885     txt = SPN_VALUE_NAME_CHARS(txt);
    886     pcNulPoint = txt;
    887     valu.valType = OPARG_TYPE_STRING;
    888 
    889     switch (*txt) {
    890     case ' ':
    891     case '\t':
    892         txt = VOIDP(parse_attrs(
    893             opts, SPN_WHITESPACE_CHARS(txt), &mode, &valu));
    894         if (txt == NULL)
    895             return txt;
    896         if (*txt == '>')
    897             break;
    898         if (*txt != '/')
    899             return NULL;
    900         /* FALLTHROUGH */
    901 
    902     case '/':
    903         if (txt[1] != '>')
    904             return NULL;
    905         *txt = NUL;
    906         txt += 2;
    907         load_opt_line(opts, ost, pzName, dir, mode);
    908         return txt;
    909 
    910     case '>':
    911         break;
    912 
    913     default:
    914         txt = strchr(txt, '>');
    915         if (txt != NULL)
    916             txt++;
    917         return txt;
    918     }
    919 
    920     /*
    921      *  If we are here, we have a value.  "txt" points to a closing angle
    922      *  bracket.  Separate the name from the value for a moment.
    923      */
    924     *pcNulPoint = NUL;
    925     pzData = ++txt;
    926     txt = trim_xml_text(txt, pzName, mode);
    927     if (txt == NULL)
    928         return txt;
    929 
    930     /*
    931      *  Rejoin the name and value for parsing by "load_opt_line()".
    932      *  Erase any attributes parsed by "parse_attrs()".
    933      */
    934     memset(pcNulPoint, ' ', (size_t)(pzData - pcNulPoint));
    935 
    936     /*
    937      *  If we are getting a "string" value that is to be cooked,
    938      *  then process the XML-ish &xx; XML-ish and %XX hex characters.
    939      */
    940     if (  (valu.valType == OPARG_TYPE_STRING)
    941        && (mode == OPTION_LOAD_COOKED))
    942         cook_xml_text(pzData);
    943 
    944     /*
    945      *  "pzName" points to what looks like text for one option/configurable.
    946      *  It is NUL terminated.  Process it.
    947      */
    948     load_opt_line(opts, ost, pzName, dir, mode);
    949 
    950     return txt;
    951 }
    952 
    953 /**
    954  *  Load a configuration file.  This may be invoked either from
    955  *  scanning the "homerc" list, or from a specific file request.
    956  *  (see "optionFileLoad()", the implementation for --load-opts)
    957  */
    958 static void
    959 intern_file_load(tOptions * opts)
    960 {
    961     uint32_t  svfl;
    962     int       idx;
    963     int       inc;
    964     char      f_name[ AG_PATH_MAX+1 ];
    965 
    966     if (opts->papzHomeList == NULL)
    967         return;
    968 
    969     svfl = opts->fOptSet;
    970     inc  = DIRECTION_PRESET;
    971 
    972     /*
    973      *  Never stop on errors in config files.
    974      */
    975     opts->fOptSet &= ~OPTPROC_ERRSTOP;
    976 
    977     /*
    978      *  Find the last RC entry (highest priority entry)
    979      */
    980     for (idx = 0; opts->papzHomeList[ idx+1 ] != NULL; ++idx)  ;
    981 
    982     /*
    983      *  For every path in the home list, ...  *TWICE* We start at the last
    984      *  (highest priority) entry, work our way down to the lowest priority,
    985      *  handling the immediate options.
    986      *  Then we go back up, doing the normal options.
    987      */
    988     for (;;) {
    989         struct stat sb;
    990         cch_t *  path;
    991 
    992         /*
    993          *  IF we've reached the bottom end, change direction
    994          */
    995         if (idx < 0) {
    996             inc = DIRECTION_PROCESS;
    997             idx = 0;
    998         }
    999 
   1000         path = opts->papzHomeList[ idx ];
   1001 
   1002         /*
   1003          *  IF we've reached the top end, bail out
   1004          */
   1005         if (path == NULL)
   1006             break;
   1007 
   1008         idx += inc;
   1009 
   1010         if (! optionMakePath(f_name, (int)sizeof(f_name),
   1011                              path, opts->pzProgPath))
   1012             continue;
   1013 
   1014         /*
   1015          *  IF the file name we constructed is a directory,
   1016          *  THEN append the Resource Configuration file name
   1017          *  ELSE we must have the complete file name
   1018          */
   1019         if (stat(f_name, &sb) != 0)
   1020             continue; /* bogus name - skip the home list entry */
   1021 
   1022         if (S_ISDIR(sb.st_mode)) {
   1023             size_t len = strlen(f_name);
   1024             size_t nln = strlen(opts->pzRcName) + 1;
   1025             char * pz  = f_name + len;
   1026 
   1027             if (len + 1 + nln >= sizeof(f_name))
   1028                 continue;
   1029 
   1030             if (pz[-1] != DIRCH)
   1031                 *(pz++) = DIRCH;
   1032             memcpy(pz, opts->pzRcName, nln);
   1033         }
   1034 
   1035         file_preset(opts, f_name, inc);
   1036 
   1037         /*
   1038          *  IF we are now to skip config files AND we are presetting,
   1039          *  THEN change direction.  We must go the other way.
   1040          */
   1041         {
   1042             tOptDesc * od = opts->pOptDesc + opts->specOptIdx.save_opts + 1;
   1043             if (DISABLED_OPT(od) && PRESETTING(inc)) {
   1044                 idx -= inc;  /* go back and reprocess current file */
   1045                 inc =  DIRECTION_PROCESS;
   1046             }
   1047         }
   1048     } /* twice for every path in the home list, ... */
   1049 
   1050     opts->fOptSet = svfl;
   1051 }
   1052 
   1053 /*=export_func optionFileLoad
   1054  *
   1055  * what: Load the locatable config files, in order
   1056  *
   1057  * arg:  + tOptions *   + opts + program options descriptor +
   1058  * arg:  + char const * + prog + program name +
   1059  *
   1060  * ret_type:  int
   1061  * ret_desc:  0 -> SUCCESS, -1 -> FAILURE
   1062  *
   1063  * doc:
   1064  *
   1065  * This function looks in all the specified directories for a configuration
   1066  * file ("rc" file or "ini" file) and processes any found twice.  The first
   1067  * time through, they are processed in reverse order (last file first).  At
   1068  * that time, only "immediate action" configurables are processed.  For
   1069  * example, if the last named file specifies not processing any more
   1070  * configuration files, then no more configuration files will be processed.
   1071  * Such an option in the @strong{first} named directory will have no effect.
   1072  *
   1073  * Once the immediate action configurables have been handled, then the
   1074  * directories are handled in normal, forward order.  In that way, later
   1075  * config files can override the settings of earlier config files.
   1076  *
   1077  * See the AutoOpts documentation for a thorough discussion of the
   1078  * config file format.
   1079  *
   1080  * Configuration files not found or not decipherable are simply ignored.
   1081  *
   1082  * err:  Returns the value, "-1" if the program options descriptor
   1083  *       is out of date or indecipherable.  Otherwise, the value "0" will
   1084  *       always be returned.
   1085 =*/
   1086 int
   1087 optionFileLoad(tOptions * opts, char const * prog)
   1088 {
   1089     if (! SUCCESSFUL(validate_struct(opts, prog)))
   1090         return -1;
   1091 
   1092     /*
   1093      * The pointer to the program name is "const".  However, the
   1094      * structure is in writable memory, so we coerce the address
   1095      * of this pointer to point to writable memory.
   1096      */
   1097     {
   1098         char const ** pp = VOIDP(&(opts->pzProgName));
   1099         *pp = prog;
   1100     }
   1101 
   1102     intern_file_load(opts);
   1103     return 0;
   1104 }
   1105 
   1106 /*=export_func  optionLoadOpt
   1107  * private:
   1108  *
   1109  * what:  Load an option rc/ini file
   1110  * arg:   + tOptions * + opts  + program options descriptor +
   1111  * arg:   + tOptDesc * + odesc + the descriptor for this arg +
   1112  *
   1113  * doc:
   1114  *  Processes the options found in the file named with
   1115  *  odesc->optArg.argString.
   1116 =*/
   1117 void
   1118 optionLoadOpt(tOptions * opts, tOptDesc * odesc)
   1119 {
   1120     struct stat sb;
   1121 
   1122     if (opts <= OPTPROC_EMIT_LIMIT)
   1123         return;
   1124 
   1125     /*
   1126      *  IF the option is not being disabled, THEN load the file.  There must
   1127      *  be a file.  (If it is being disabled, then the disablement processing
   1128      *  already took place.  It must be done to suppress preloading of ini/rc
   1129      *  files.)
   1130      */
   1131     if (  DISABLED_OPT(odesc)
   1132        || ((odesc->fOptState & OPTST_RESET) != 0))
   1133         return;
   1134 
   1135     if (stat(odesc->optArg.argString, &sb) != 0) {
   1136         if ((opts->fOptSet & OPTPROC_ERRSTOP) == 0)
   1137             return;
   1138 
   1139         fserr_exit(opts->pzProgName, "stat", odesc->optArg.argString);
   1140         /* NOT REACHED */
   1141     }
   1142 
   1143     if (! S_ISREG(sb.st_mode)) {
   1144         if ((opts->fOptSet & OPTPROC_ERRSTOP) == 0)
   1145             return;
   1146         errno = EINVAL;
   1147         fserr_exit(opts->pzProgName, "stat", odesc->optArg.argString);
   1148         /* NOT REACHED */
   1149     }
   1150 
   1151     file_preset(opts, odesc->optArg.argString, DIRECTION_CALLED);
   1152 }
   1153 
   1154 /**
   1155  *  Parse the various attributes of an XML-styled config file entry
   1156  *
   1157  * @returns NULL on failure, otherwise the scan point
   1158  */
   1159 static char const *
   1160 parse_attrs(tOptions * opts, char const * txt, tOptionLoadMode * pMode,
   1161             tOptionValue * pType)
   1162 {
   1163     size_t len = 0;
   1164 
   1165     for (;;) {
   1166         len = (size_t)(SPN_LOWER_CASE_CHARS(txt) - txt);
   1167 
   1168         /*
   1169          * The enumeration used in this switch is derived from this switch
   1170          * statement itself.  The "find_option_xat_attribute_cmd" function
   1171          * will return XAT_CMD_MEMBERS for the "txt" string value
   1172          * "members", etc.
   1173          */
   1174         switch (find_option_xat_attribute_cmd(txt, len)) {
   1175         case XAT_CMD_TYPE:
   1176             txt = parse_value(txt+len, pType);
   1177             break;
   1178 
   1179         case XAT_CMD_WORDS:
   1180             txt = parse_keyword(opts, txt+len, pType);
   1181             break;
   1182 
   1183         case XAT_CMD_MEMBERS:
   1184             txt = parse_set_mem(opts, txt+len, pType);
   1185             break;
   1186 
   1187         case XAT_CMD_COOKED:
   1188             txt += len;
   1189             if (! IS_END_XML_TOKEN_CHAR(*txt))
   1190                 goto invalid_kwd;
   1191 
   1192             *pMode = OPTION_LOAD_COOKED;
   1193             break;
   1194 
   1195         case XAT_CMD_UNCOOKED:
   1196             txt += len;
   1197             if (! IS_END_XML_TOKEN_CHAR(*txt))
   1198                 goto invalid_kwd;
   1199 
   1200             *pMode = OPTION_LOAD_UNCOOKED;
   1201             break;
   1202 
   1203         case XAT_CMD_KEEP:
   1204             txt += len;
   1205             if (! IS_END_XML_TOKEN_CHAR(*txt))
   1206                 goto invalid_kwd;
   1207 
   1208             *pMode = OPTION_LOAD_KEEP;
   1209             break;
   1210 
   1211         default:
   1212         case XAT_INVALID_CMD:
   1213         invalid_kwd:
   1214             pType->valType = OPARG_TYPE_NONE;
   1215             return skip_unkn(txt);
   1216         }
   1217 
   1218         if (txt == NULL)
   1219             return NULL;
   1220         txt = SPN_WHITESPACE_CHARS(txt);
   1221         switch (*txt) {
   1222             case '/': pType->valType = OPARG_TYPE_NONE;
   1223                       /* FALLTHROUGH */
   1224             case '>': return txt;
   1225         }
   1226         if (! IS_LOWER_CASE_CHAR(*txt))
   1227             return NULL;
   1228     }
   1229 }
   1230 
   1231 /**
   1232  *  "txt" points to the character after "words=".
   1233  *  What should follow is a name of a keyword (enumeration) list.
   1234  *
   1235  *  @param     opts  unused
   1236  *  @param[in] txt   keyword to skip over
   1237  *  @param     type  unused value type
   1238  *  @returns   pointer after skipped text
   1239  */
   1240 static char const *
   1241 parse_keyword(tOptions * opts, char const * txt, tOptionValue * typ)
   1242 {
   1243     (void)opts;
   1244     (void)typ;
   1245 
   1246     return skip_unkn(txt);
   1247 }
   1248 
   1249 /**
   1250  *  "txt" points to the character after "members="
   1251  *  What should follow is a name of a "set membership".
   1252  *  A collection of bit flags.
   1253  *
   1254  *  @param     opts  unused
   1255  *  @param[in] txt   keyword to skip over
   1256  *  @param     type  unused value type
   1257  *  @returns   pointer after skipped text
   1258  */
   1259 static char const *
   1260 parse_set_mem(tOptions * opts, char const * txt, tOptionValue * typ)
   1261 {
   1262     (void)opts;
   1263     (void)typ;
   1264 
   1265     return skip_unkn(txt);
   1266 }
   1267 
   1268 /**
   1269  *  parse the type.  The keyword "type" was found, now figure out
   1270  *  the type that follows the type.
   1271  *
   1272  *  @param[in]  txt  points to the '=' character after the "type" keyword.
   1273  *  @param[out] typ  where to store the type found
   1274  *  @returns    the next byte after the type name
   1275  */
   1276 static char const *
   1277 parse_value(char const * txt, tOptionValue * typ)
   1278 {
   1279     size_t len = 0;
   1280 
   1281     if (*(txt++) != '=')
   1282         goto woops;
   1283 
   1284     len = (size_t)(SPN_OPTION_NAME_CHARS(txt) - txt);
   1285 
   1286     if ((len == 0) || (! IS_END_XML_TOKEN_CHAR(txt[len]))) {
   1287     woops:
   1288         typ->valType = OPARG_TYPE_NONE;
   1289         return skip_unkn(txt + len);
   1290     }
   1291 
   1292     /*
   1293      * The enumeration used in this switch is derived from this switch
   1294      * statement itself.  The "find_option_value_type_cmd" function
   1295      * will return VTP_CMD_INTEGER for the "txt" string value
   1296      * "integer", etc.
   1297      */
   1298     switch (find_option_value_type_cmd(txt, len)) {
   1299     default:
   1300     case VTP_INVALID_CMD: goto woops;
   1301 
   1302     case VTP_CMD_STRING:
   1303         typ->valType = OPARG_TYPE_STRING;
   1304         break;
   1305 
   1306     case VTP_CMD_INTEGER:
   1307         typ->valType = OPARG_TYPE_NUMERIC;
   1308         break;
   1309 
   1310     case VTP_CMD_BOOL:
   1311     case VTP_CMD_BOOLEAN:
   1312         typ->valType = OPARG_TYPE_BOOLEAN;
   1313         break;
   1314 
   1315     case VTP_CMD_KEYWORD:
   1316         typ->valType = OPARG_TYPE_ENUMERATION;
   1317         break;
   1318 
   1319     case VTP_CMD_SET:
   1320     case VTP_CMD_SET_MEMBERSHIP:
   1321         typ->valType = OPARG_TYPE_MEMBERSHIP;
   1322         break;
   1323 
   1324     case VTP_CMD_NESTED:
   1325     case VTP_CMD_HIERARCHY:
   1326         typ->valType = OPARG_TYPE_HIERARCHY;
   1327     }
   1328 
   1329     return txt + len;
   1330 }
   1331 
   1332 /** @}
   1333  *
   1334  * Local Variables:
   1335  * mode: C
   1336  * c-file-style: "stroustrup"
   1337  * indent-tabs-mode: nil
   1338  * End:
   1339  * end of autoopts/configfile.c */
   1340