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