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