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