xref: /netbsd-src/external/bsd/ntp/dist/sntp/libopts/configfile.c (revision eabc0478de71e4e011a5b4e0392741e01d491794)
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