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 * 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 * 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 * 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 * 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 * 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 * 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 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 * 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 * 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 * 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 * 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 * 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 * 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 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 * 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 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 * 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 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 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 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 * 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 * 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 * 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 * 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