1 2 /* 3 * $Id: nested.c,v 4.14 2007/02/04 17:44:12 bkorb Exp $ 4 * Time-stamp: "2007-01-26 11:04:35 bkorb" 5 * 6 * Automated Options Nested Values module. 7 */ 8 9 /* 10 * Automated Options copyright 1992-2007 Bruce Korb 11 * 12 * Automated Options is free software. 13 * You may redistribute it and/or modify it under the terms of the 14 * GNU General Public License, as published by the Free Software 15 * Foundation; either version 2, or (at your option) any later version. 16 * 17 * Automated Options is distributed in the hope that it will be useful, 18 * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 * GNU General Public License for more details. 21 * 22 * You should have received a copy of the GNU General Public License 23 * along with Automated Options. See the file "COPYING". If not, 24 * write to: The Free Software Foundation, Inc., 25 * 51 Franklin Street, Fifth Floor, 26 * Boston, MA 02110-1301, USA. 27 * 28 * As a special exception, Bruce Korb gives permission for additional 29 * uses of the text contained in his release of AutoOpts. 30 * 31 * The exception is that, if you link the AutoOpts library with other 32 * files to produce an executable, this does not by itself cause the 33 * resulting executable to be covered by the GNU General Public License. 34 * Your use of that executable is in no way restricted on account of 35 * linking the AutoOpts library code into it. 36 * 37 * This exception does not however invalidate any other reasons why 38 * the executable file might be covered by the GNU General Public License. 39 * 40 * This exception applies only to the code released by Bruce Korb under 41 * the name AutoOpts. If you copy code from other sources under the 42 * General Public License into a copy of AutoOpts, as the General Public 43 * License permits, the exception does not apply to the code that you add 44 * in this way. To avoid misleading anyone as to the status of such 45 * modified files, you must delete this exception notice from them. 46 * 47 * If you write modifications of your own for AutoOpts, it is your choice 48 * whether to permit this exception to apply to your modifications. 49 * If you do not wish that, delete this exception notice. 50 */ 51 /* = = = START-STATIC-FORWARD = = = */ 52 /* static forward declarations maintained by :mkfwd */ 53 static void 54 removeBackslashes( char* pzSrc ); 55 56 static char const* 57 scanQuotedString( char const* pzTxt ); 58 59 static tOptionValue* 60 addStringValue( void** pp, char const* pzName, size_t nameLen, 61 char const* pzValue, size_t dataLen ); 62 63 static tOptionValue* 64 addBoolValue( void** pp, char const* pzName, size_t nameLen, 65 char const* pzValue, size_t dataLen ); 66 67 static tOptionValue* 68 addNumberValue( void** pp, char const* pzName, size_t nameLen, 69 char const* pzValue, size_t dataLen ); 70 71 static tOptionValue* 72 addNestedValue( void** pp, char const* pzName, size_t nameLen, 73 char* pzValue, size_t dataLen ); 74 75 static char const* 76 scanNameEntry(char const* pzName, tOptionValue* pRes); 77 78 static char const* 79 scanXmlEntry( char const* pzName, tOptionValue* pRes ); 80 81 static void 82 unloadNestedArglist( tArgList* pAL ); 83 84 static void 85 sortNestedList( tArgList* pAL ); 86 /* = = = END-STATIC-FORWARD = = = */ 87 88 /* removeBackslashes 89 * 90 * This function assumes that all newline characters were preceeded by 91 * backslashes that need removal. 92 */ 93 static void 94 removeBackslashes( char* pzSrc ) 95 { 96 char* pzD = strchr(pzSrc, '\n'); 97 98 if (pzD == NULL) 99 return; 100 *--pzD = '\n'; 101 102 for (;;) { 103 char ch = ((*pzD++) = *(pzSrc++)); 104 switch (ch) { 105 case '\n': *--pzD = ch; break; 106 case NUL: return; 107 default: 108 ; 109 } 110 } 111 } 112 113 114 /* scanQuotedString 115 * 116 * Find the end of a quoted string, skipping escaped quote characters. 117 */ 118 static char const* 119 scanQuotedString( char const* pzTxt ) 120 { 121 char q = *(pzTxt++); /* remember the type of quote */ 122 123 for (;;) { 124 char ch = *(pzTxt++); 125 if (ch == NUL) 126 return pzTxt-1; 127 128 if (ch == q) 129 return pzTxt; 130 131 if (ch == '\\') { 132 ch = *(pzTxt++); 133 /* 134 * IF the next character is NUL, drop the backslash, too. 135 */ 136 if (ch == NUL) 137 return pzTxt - 2; 138 139 /* 140 * IF the quote character or the escape character were escaped, 141 * then skip both, as long as the string does not end. 142 */ 143 if ((ch == q) || (ch == '\\')) { 144 if (*(pzTxt++) == NUL) 145 return pzTxt-1; 146 } 147 } 148 } 149 } 150 151 152 /* addStringValue 153 * 154 * Associate a name with either a string or no value. 155 */ 156 static tOptionValue* 157 addStringValue( void** pp, char const* pzName, size_t nameLen, 158 char const* pzValue, size_t dataLen ) 159 { 160 tOptionValue* pNV; 161 size_t sz = nameLen + dataLen + sizeof(*pNV); 162 163 pNV = AGALOC( sz, "option name/str value pair" ); 164 if (pNV == NULL) 165 return NULL; 166 167 if (pzValue == NULL) { 168 pNV->valType = OPARG_TYPE_NONE; 169 pNV->pzName = pNV->v.strVal; 170 171 } else { 172 pNV->valType = OPARG_TYPE_STRING; 173 if (dataLen > 0) 174 memcpy( pNV->v.strVal, pzValue, dataLen ); 175 pNV->v.strVal[dataLen] = NUL; 176 pNV->pzName = pNV->v.strVal + dataLen + 1; 177 } 178 179 memcpy( pNV->pzName, pzName, nameLen ); 180 pNV->pzName[ nameLen ] = NUL; 181 addArgListEntry( pp, pNV ); 182 return pNV; 183 } 184 185 186 /* addBoolValue 187 * 188 * Associate a name with either a string or no value. 189 */ 190 static tOptionValue* 191 addBoolValue( void** pp, char const* pzName, size_t nameLen, 192 char const* pzValue, size_t dataLen ) 193 { 194 tOptionValue* pNV; 195 size_t sz = nameLen + sizeof(*pNV) + 1; 196 197 pNV = AGALOC( sz, "option name/bool value pair" ); 198 if (pNV == NULL) 199 return NULL; 200 while (isspace( (int)*pzValue ) && (dataLen > 0)) { 201 dataLen--; pzValue++; 202 } 203 if (dataLen == 0) 204 pNV->v.boolVal = 0; 205 else if (isdigit( (int)*pzValue )) 206 pNV->v.boolVal = atoi( pzValue ); 207 else switch (*pzValue) { 208 case 'f': 209 case 'F': 210 case 'n': 211 case 'N': 212 pNV->v.boolVal = 0; break; 213 default: 214 pNV->v.boolVal = 1; 215 } 216 217 pNV->valType = OPARG_TYPE_BOOLEAN; 218 pNV->pzName = (char*)(pNV + 1); 219 memcpy( pNV->pzName, pzName, nameLen ); 220 pNV->pzName[ nameLen ] = NUL; 221 addArgListEntry( pp, pNV ); 222 return pNV; 223 } 224 225 226 /* addNumberValue 227 * 228 * Associate a name with either a string or no value. 229 */ 230 static tOptionValue* 231 addNumberValue( void** pp, char const* pzName, size_t nameLen, 232 char const* pzValue, size_t dataLen ) 233 { 234 tOptionValue* pNV; 235 size_t sz = nameLen + sizeof(*pNV) + 1; 236 237 pNV = AGALOC( sz, "option name/bool value pair" ); 238 if (pNV == NULL) 239 return NULL; 240 while (isspace( (int)*pzValue ) && (dataLen > 0)) { 241 dataLen--; pzValue++; 242 } 243 if (dataLen == 0) 244 pNV->v.boolVal = 0; 245 else 246 pNV->v.boolVal = atoi( pzValue ); 247 248 pNV->valType = OPARG_TYPE_NUMERIC; 249 pNV->pzName = (char*)(pNV + 1); 250 memcpy( pNV->pzName, pzName, nameLen ); 251 pNV->pzName[ nameLen ] = NUL; 252 addArgListEntry( pp, pNV ); 253 return pNV; 254 } 255 256 257 /* addNestedValue 258 * 259 * Associate a name with either a string or no value. 260 */ 261 static tOptionValue* 262 addNestedValue( void** pp, char const* pzName, size_t nameLen, 263 char* pzValue, size_t dataLen ) 264 { 265 tOptionValue* pNV; 266 267 if (dataLen == 0) { 268 size_t sz = nameLen + sizeof(*pNV) + 1; 269 pNV = AGALOC( sz, "empty nested value pair" ); 270 if (pNV == NULL) 271 return NULL; 272 pNV->v.nestVal = NULL; 273 pNV->valType = OPARG_TYPE_HIERARCHY; 274 pNV->pzName = (char*)(pNV + 1); 275 memcpy( pNV->pzName, pzName, nameLen ); 276 pNV->pzName[ nameLen ] = NUL; 277 278 } else { 279 pNV = optionLoadNested( pzValue, pzName, nameLen ); 280 } 281 282 if (pNV != NULL) 283 addArgListEntry( pp, pNV ); 284 285 return pNV; 286 } 287 288 289 /* scanNameEntry 290 * 291 * We have an entry that starts with a name. Find the end of it, cook it 292 * (if called for) and create the name/value association. 293 */ 294 static char const* 295 scanNameEntry(char const* pzName, tOptionValue* pRes) 296 { 297 tOptionValue* pNV; 298 char const * pzScan = pzName+1; 299 char const * pzVal; 300 size_t nameLen = 1; 301 size_t dataLen = 0; 302 303 while (ISNAMECHAR( (int)*pzScan )) { pzScan++; nameLen++; } 304 305 while (isspace( (int)*pzScan )) { 306 char ch = *(pzScan++); 307 if ((ch == '\n') || (ch == ',')) { 308 addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL,(size_t)0); 309 return pzScan - 1; 310 } 311 } 312 313 switch (*pzScan) { 314 case '=': 315 case ':': 316 while (isspace( (int)*++pzScan )) ; 317 switch (*pzScan) { 318 case ',': goto comma_char; 319 case '"': 320 case '\'': goto quote_char; 321 case NUL: goto nul_byte; 322 default: goto default_char; 323 } 324 325 case ',': 326 comma_char: 327 pzScan++; 328 /* FALLTHROUGH */ 329 330 case NUL: 331 nul_byte: 332 addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0); 333 break; 334 335 case '"': 336 case '\'': 337 quote_char: 338 pzVal = pzScan; 339 pzScan = scanQuotedString( pzScan ); 340 dataLen = pzScan - pzVal; 341 pNV = addStringValue( &(pRes->v.nestVal), pzName, nameLen, pzVal, 342 dataLen ); 343 if ((pNV != NULL) && (option_load_mode == OPTION_LOAD_COOKED)) 344 ao_string_cook( pNV->v.strVal, NULL ); 345 break; 346 347 default: 348 default_char: 349 /* 350 * We have found some strange text value. It ends with a newline 351 * or a comma. 352 */ 353 pzVal = pzScan; 354 for (;;) { 355 char ch = *(pzScan++); 356 switch (ch) { 357 case NUL: 358 pzScan--; 359 dataLen = pzScan - pzVal; 360 goto string_done; 361 /* FALLTHROUGH */ 362 363 case '\n': 364 if ( (pzScan > pzVal + 2) 365 && (pzScan[-2] == '\\') 366 && (pzScan[ 0] != NUL)) 367 continue; 368 /* FALLTHROUGH */ 369 370 case ',': 371 dataLen = (pzScan - pzVal) - 1; 372 string_done: 373 pNV = addStringValue( &(pRes->v.nestVal), pzName, nameLen, 374 pzVal, dataLen ); 375 if (pNV != NULL) 376 removeBackslashes( pNV->v.strVal ); 377 goto leave_scan_name; 378 } 379 } 380 break; 381 } leave_scan_name:; 382 383 return pzScan; 384 } 385 386 387 /* scanXmlEntry 388 * 389 * We've found a '<' character. We ignore this if it is a comment or a 390 * directive. If it is something else, then whatever it is we are looking 391 * at is bogus. Returning NULL stops processing. 392 */ 393 static char const* 394 scanXmlEntry( char const* pzName, tOptionValue* pRes ) 395 { 396 size_t nameLen = 1, valLen = 0; 397 char const* pzScan = ++pzName; 398 char const* pzVal; 399 tOptionValue valu; 400 tOptionValue* pNewVal; 401 tOptionLoadMode save_mode = option_load_mode; 402 403 if (! isalpha((int)*pzName)) { 404 switch (*pzName) { 405 default: 406 pzName = NULL; 407 break; 408 409 case '!': 410 pzName = strstr( pzName, "-->" ); 411 if (pzName != NULL) 412 pzName += 3; 413 break; 414 415 case '?': 416 pzName = strchr( pzName, '>' ); 417 if (pzName != NULL) 418 pzName++; 419 break; 420 } 421 return pzName; 422 } 423 424 while (isalpha( (int)*++pzScan )) nameLen++; 425 if (nameLen > 64) 426 return NULL; 427 valu.valType = OPARG_TYPE_STRING; 428 429 switch (*pzScan) { 430 case ' ': 431 case '\t': 432 pzScan = parseAttributes( 433 NULL, (char*)pzScan, &option_load_mode, &valu ); 434 if (*pzScan == '>') { 435 pzScan++; 436 break; 437 } 438 439 if (*pzScan != '/') { 440 option_load_mode = save_mode; 441 return NULL; 442 } 443 /* FALLTHROUGH */ 444 445 case '/': 446 if (*++pzScan != '>') { 447 option_load_mode = save_mode; 448 return NULL; 449 } 450 addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0); 451 option_load_mode = save_mode; 452 return pzScan+2; 453 454 default: 455 option_load_mode = save_mode; 456 return NULL; 457 458 case '>': 459 pzScan++; 460 break; 461 } 462 463 pzVal = pzScan; 464 465 { 466 char z[68]; 467 char* pzD = z; 468 int ct = nameLen; 469 char const* pzS = pzName; 470 471 *(pzD++) = '<'; 472 *(pzD++) = '/'; 473 474 do { 475 *(pzD++) = *(pzS++); 476 } while (--ct > 0); 477 *(pzD++) = '>'; 478 *pzD = NUL; 479 480 pzScan = strstr( pzScan, z ); 481 if (pzScan == NULL) { 482 option_load_mode = save_mode; 483 return NULL; 484 } 485 valLen = (pzScan - pzVal); 486 pzScan += nameLen + 3; 487 while (isspace( (int)*pzScan )) pzScan++; 488 } 489 490 switch (valu.valType) { 491 case OPARG_TYPE_NONE: 492 addStringValue( &(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0); 493 break; 494 495 case OPARG_TYPE_STRING: 496 pNewVal = addStringValue( 497 &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen); 498 499 if (option_load_mode == OPTION_LOAD_KEEP) 500 break; 501 mungeString( pNewVal->v.strVal, option_load_mode ); 502 break; 503 504 case OPARG_TYPE_BOOLEAN: 505 addBoolValue( &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen ); 506 break; 507 508 case OPARG_TYPE_NUMERIC: 509 addNumberValue( &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen ); 510 break; 511 512 case OPARG_TYPE_HIERARCHY: 513 { 514 char* pz = AGALOC( valLen+1, "hierarchical scan" ); 515 if (pz == NULL) 516 break; 517 memcpy( pz, pzVal, valLen ); 518 pz[valLen] = NUL; 519 addNestedValue( &(pRes->v.nestVal), pzName, nameLen, pz, valLen ); 520 AGFREE(pz); 521 break; 522 } 523 524 case OPARG_TYPE_ENUMERATION: 525 case OPARG_TYPE_MEMBERSHIP: 526 default: 527 break; 528 } 529 530 option_load_mode = save_mode; 531 return pzScan; 532 } 533 534 535 /* unloadNestedArglist 536 * 537 * Deallocate a list of option arguments. This must have been gotten from 538 * a hierarchical option argument, not a stacked list of strings. It is 539 * an internal call, so it is not validated. The caller is responsible for 540 * knowing what they are doing. 541 */ 542 static void 543 unloadNestedArglist( tArgList* pAL ) 544 { 545 int ct = pAL->useCt; 546 tCC** ppNV = pAL->apzArgs; 547 548 while (ct-- > 0) { 549 tOptionValue* pNV = (tOptionValue*)(void*)*(ppNV++); 550 if (pNV->valType == OPARG_TYPE_HIERARCHY) 551 unloadNestedArglist( pNV->v.nestVal ); 552 AGFREE( pNV ); 553 } 554 555 AGFREE( (void*)pAL ); 556 } 557 558 559 /*=export_func optionUnloadNested 560 * 561 * what: Deallocate the memory for a nested value 562 * arg: + tOptionValue const * + pOptVal + the hierarchical value + 563 * 564 * doc: 565 * A nested value needs to be deallocated. The pointer passed in should 566 * have been gotten from a call to @code{configFileLoad()} (See 567 * @pxref{libopts-configFileLoad}). 568 =*/ 569 void 570 optionUnloadNested( tOptionValue const * pOV ) 571 { 572 if (pOV == NULL) return; 573 if (pOV->valType != OPARG_TYPE_HIERARCHY) { 574 errno = EINVAL; 575 return; 576 } 577 578 unloadNestedArglist( pOV->v.nestVal ); 579 580 AGFREE( (void*)pOV ); 581 } 582 583 584 /* sortNestedList 585 * 586 * This is a _stable_ sort. The entries are sorted alphabetically, 587 * but within entries of the same name the ordering is unchanged. 588 * Typically, we also hope the input is sorted. 589 */ 590 static void 591 sortNestedList( tArgList* pAL ) 592 { 593 int ix; 594 int lm = pAL->useCt; 595 596 /* 597 * This loop iterates "useCt" - 1 times. 598 */ 599 for (ix = 0; ++ix < lm;) { 600 int iy = ix-1; 601 tOptionValue* pNewNV = (tOptionValue*)(void*)(pAL->apzArgs[ix]); 602 tOptionValue* pOldNV = (tOptionValue*)(void*)(pAL->apzArgs[iy]); 603 604 /* 605 * For as long as the new entry precedes the "old" entry, 606 * move the old pointer. Stop before trying to extract the 607 * "-1" entry. 608 */ 609 while (strcmp( pOldNV->pzName, pNewNV->pzName ) > 0) { 610 pAL->apzArgs[iy+1] = (void*)pOldNV; 611 pOldNV = (tOptionValue*)(void*)(pAL->apzArgs[--iy]); 612 if (iy < 0) 613 break; 614 } 615 616 /* 617 * Always store the pointer. Sometimes it is redundant, 618 * but the redundancy is cheaper than a test and branch sequence. 619 */ 620 pAL->apzArgs[iy+1] = (void*)pNewNV; 621 } 622 } 623 624 625 /* optionLoadNested 626 * private: 627 * 628 * what: parse a hierarchical option argument 629 * arg: + char const* + pzTxt + the text to scan + 630 * arg: + char const* + pzName + the name for the text + 631 * arg: + size_t + nameLen + the length of "name" + 632 * 633 * ret_type: tOptionValue* 634 * ret_desc: An allocated, compound value structure 635 * 636 * doc: 637 * A block of text represents a series of values. It may be an 638 * entire configuration file, or it may be an argument to an 639 * option that takes a hierarchical value. 640 */ 641 LOCAL tOptionValue* 642 optionLoadNested(char const* pzTxt, char const* pzName, size_t nameLen) 643 { 644 tOptionValue* pRes; 645 tArgList* pAL; 646 647 /* 648 * Make sure we have some data and we have space to put what we find. 649 */ 650 if (pzTxt == NULL) { 651 errno = EINVAL; 652 return NULL; 653 } 654 while (isspace( (int)*pzTxt )) pzTxt++; 655 if (*pzTxt == NUL) { 656 errno = ENOENT; 657 return NULL; 658 } 659 pRes = AGALOC( sizeof(*pRes) + nameLen + 1, "nested args" ); 660 if (pRes == NULL) { 661 errno = ENOMEM; 662 return NULL; 663 } 664 pRes->valType = OPARG_TYPE_HIERARCHY; 665 pRes->pzName = (char*)(pRes + 1); 666 memcpy( pRes->pzName, pzName, nameLen ); 667 pRes->pzName[ nameLen ] = NUL; 668 669 pAL = AGALOC( sizeof(*pAL), "nested arg list" ); 670 if (pAL == NULL) { 671 AGFREE( pRes ); 672 return NULL; 673 } 674 pRes->v.nestVal = pAL; 675 pAL->useCt = 0; 676 pAL->allocCt = MIN_ARG_ALLOC_CT; 677 678 /* 679 * Scan until we hit a NUL. 680 */ 681 do { 682 while (isspace( (int)*pzTxt )) pzTxt++; 683 if (isalpha( (int)*pzTxt )) { 684 pzTxt = scanNameEntry( pzTxt, pRes ); 685 } 686 else switch (*pzTxt) { 687 case NUL: goto scan_done; 688 case '<': pzTxt = scanXmlEntry( pzTxt, pRes ); 689 if (*pzTxt == ',') pzTxt++; break; 690 case '#': pzTxt = strchr( pzTxt, '\n' ); break; 691 default: goto woops; 692 } 693 } while (pzTxt != NULL); scan_done:; 694 695 pAL = pRes->v.nestVal; 696 if (pAL->useCt != 0) { 697 sortNestedList( pAL ); 698 return pRes; 699 } 700 701 woops: 702 AGFREE( pRes->v.nestVal ); 703 AGFREE( pRes ); 704 return NULL; 705 } 706 707 708 /*=export_func optionNestedVal 709 * private: 710 * 711 * what: parse a hierarchical option argument 712 * arg: + tOptions* + pOpts + program options descriptor + 713 * arg: + tOptDesc* + pOptDesc + the descriptor for this arg + 714 * 715 * doc: 716 * Nested value was found on the command line 717 =*/ 718 void 719 optionNestedVal( tOptions* pOpts, tOptDesc* pOD ) 720 { 721 tOptionValue* pOV = optionLoadNested( 722 pOD->optArg.argString, pOD->pz_Name, strlen(pOD->pz_Name)); 723 724 if (pOV != NULL) 725 addArgListEntry( &(pOD->optCookie), (void*)pOV ); 726 } 727 /* 728 * Local Variables: 729 * mode: C 730 * c-file-style: "stroustrup" 731 * indent-tabs-mode: nil 732 * End: 733 * end of autoopts/nested.c */ 734