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