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