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