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