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