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