configfile.c revision 181834
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 */
53static void
54filePreset(
55    tOptions*     pOpts,
56    char const*   pzFileName,
57    int           direction );
58
59static char*
60handleComment( char* pzText );
61
62static char*
63handleConfig(
64    tOptions*     pOpts,
65    tOptState*    pOS,
66    char*         pzText,
67    int           direction );
68
69static char*
70handleDirective(
71    tOptions*     pOpts,
72    char*         pzText );
73
74static char*
75handleProgramSection(
76    tOptions*     pOpts,
77    char*         pzText );
78
79static char*
80handleStructure(
81    tOptions*     pOpts,
82    tOptState*    pOS,
83    char*         pzText,
84    int           direction );
85
86static char*
87parseKeyWordType(
88    tOptions*     pOpts,
89    char*         pzText,
90    tOptionValue* pType );
91
92static char*
93parseLoadMode(
94    char*               pzText,
95    tOptionLoadMode*    pMode );
96
97static char*
98parseSetMemType(
99    tOptions*     pOpts,
100    char*         pzText,
101    tOptionValue* pType );
102
103static char*
104parseValueType(
105    char*         pzText,
106    tOptionValue* pType );
107
108static char*
109skipUnknown( 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=*/
143const tOptionValue*
144configFileLoad( 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=*/
195const tOptionValue*
196optionFindValue( 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=*/
271const tOptionValue*
272optionFindNextValue( 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=*/
339const tOptionValue*
340optionGetValue( 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=*/
400tOptionValue const *
401optionNextValue(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 */
440static void
441filePreset(
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 */
522static char*
523handleComment( 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 */
539static char*
540handleConfig(
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 */
624static char*
625handleDirective(
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 */
670static char*
671handleProgramSection(
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 */
701static char*
702handleStructure(
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 */
798LOCAL void
799internalFileLoad( 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=*/
916int
917optionFileLoad( 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=*/
939void
940optionLoadOpt( 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 */
978LOCAL char*
979parseAttributes(
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 */
1035static char*
1036parseKeyWordType(
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 */
1051static char*
1052parseLoadMode(
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 */
1106static char*
1107parseSetMemType(
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 */
1120static char*
1121parseValueType(
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 */
1201static char*
1202skipUnknown( 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 */
1223LOCAL tSuccess
1224validateOptionsStruct( 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