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