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