PageRenderTime 105ms CodeModel.GetById 22ms app.highlight 75ms RepoModel.GetById 0ms app.codeStats 1ms

/contrib/ntp/libopts/configfile.c

https://bitbucket.org/freebsd/freebsd-head/
C | 1290 lines | 715 code | 177 blank | 398 comment | 244 complexity | be8d60ab77006d454f5350c8390cb44d MD5 | raw file
   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 */
  53static void
  54filePreset(
  55    tOptions*     pOpts,
  56    char const*   pzFileName,
  57    int           direction );
  58
  59static char*
  60handleComment( char* pzText );
  61
  62static char*
  63handleConfig(
  64    tOptions*     pOpts,
  65    tOptState*    pOS,
  66    char*         pzText,
  67    int           direction );
  68
  69static char*
  70handleDirective(
  71    tOptions*     pOpts,
  72    char*         pzText );
  73
  74static char*
  75handleProgramSection(
  76    tOptions*     pOpts,
  77    char*         pzText );
  78
  79static char*
  80handleStructure(
  81    tOptions*     pOpts,
  82    tOptState*    pOS,
  83    char*         pzText,
  84    int           direction );
  85
  86static char*
  87parseKeyWordType(
  88    tOptions*     pOpts,
  89    char*         pzText,
  90    tOptionValue* pType );
  91
  92static char*
  93parseLoadMode(
  94    char*               pzText,
  95    tOptionLoadMode*    pMode );
  96
  97static char*
  98parseSetMemType(
  99    tOptions*     pOpts,
 100    char*         pzText,
 101    tOptionValue* pType );
 102
 103static char*
 104parseValueType(
 105    char*         pzText,
 106    tOptionValue* pType );
 107
 108static char*
 109skipUnknown( 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=*/
 143const tOptionValue*
 144configFileLoad( 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=*/
 195const tOptionValue*
 196optionFindValue( 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=*/
 271const tOptionValue*
 272optionFindNextValue( 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=*/
 339const tOptionValue*
 340optionGetValue( 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=*/
 400tOptionValue const *
 401optionNextValue(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 */
 440static void
 441filePreset(
 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 */
 522static char*
 523handleComment( 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 */
 539static char*
 540handleConfig(
 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 */
 624static char*
 625handleDirective(
 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 */
 670static char*
 671handleProgramSection(
 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 */
 701static char*
 702handleStructure(
 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 */
 798LOCAL void
 799internalFileLoad( 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=*/
 916int
 917optionFileLoad( 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=*/
 939void
 940optionLoadOpt( 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 */
 978LOCAL char*
 979parseAttributes(
 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 */
1035static char*
1036parseKeyWordType(
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 */
1051static char*
1052parseLoadMode(
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 */
1106static char*
1107parseSetMemType(
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 */
1120static char*
1121parseValueType(
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 */
1201static char*
1202skipUnknown( 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 */
1223LOCAL tSuccess
1224validateOptionsStruct( 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 */