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