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