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