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