diff options
Diffstat (limited to 'contrib/ntp/libopts/nested.c')
-rw-r--r-- | contrib/ntp/libopts/nested.c | 733 |
1 files changed, 733 insertions, 0 deletions
diff --git a/contrib/ntp/libopts/nested.c b/contrib/ntp/libopts/nested.c new file mode 100644 index 0000000..b39f8d1 --- /dev/null +++ b/contrib/ntp/libopts/nested.c @@ -0,0 +1,733 @@ + +/* + * $Id: nested.c,v 4.14 2007/02/04 17:44:12 bkorb Exp $ + * Time-stamp: "2007-01-26 11:04:35 bkorb" + * + * Automated Options Nested Values module. + */ + +/* + * Automated Options copyright 1992-2007 Bruce Korb + * + * Automated Options is free software. + * You may redistribute it and/or modify it under the terms of the + * GNU General Public License, as published by the Free Software + * Foundation; either version 2, or (at your option) any later version. + * + * Automated Options is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Automated Options. See the file "COPYING". If not, + * write to: The Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * As a special exception, Bruce Korb gives permission for additional + * uses of the text contained in his release of AutoOpts. + * + * The exception is that, if you link the AutoOpts library with other + * files to produce an executable, this does not by itself cause the + * resulting executable to be covered by the GNU General Public License. + * Your use of that executable is in no way restricted on account of + * linking the AutoOpts library code into it. + * + * This exception does not however invalidate any other reasons why + * the executable file might be covered by the GNU General Public License. + * + * This exception applies only to the code released by Bruce Korb under + * the name AutoOpts. If you copy code from other sources under the + * General Public License into a copy of AutoOpts, as the General Public + * License permits, the exception does not apply to the code that you add + * in this way. To avoid misleading anyone as to the status of such + * modified files, you must delete this exception notice from them. + * + * If you write modifications of your own for AutoOpts, it is your choice + * whether to permit this exception to apply to your modifications. + * If you do not wish that, delete this exception notice. + */ +/* = = = START-STATIC-FORWARD = = = */ +/* static forward declarations maintained by :mkfwd */ +static void +removeBackslashes( char* pzSrc ); + +static char const* +scanQuotedString( char const* pzTxt ); + +static tOptionValue* +addStringValue( void** pp, char const* pzName, size_t nameLen, + char const* pzValue, size_t dataLen ); + +static tOptionValue* +addBoolValue( void** pp, char const* pzName, size_t nameLen, + char const* pzValue, size_t dataLen ); + +static tOptionValue* +addNumberValue( void** pp, char const* pzName, size_t nameLen, + char const* pzValue, size_t dataLen ); + +static tOptionValue* +addNestedValue( void** pp, char const* pzName, size_t nameLen, + char* pzValue, size_t dataLen ); + +static char const* +scanNameEntry(char const* pzName, tOptionValue* pRes); + +static char const* +scanXmlEntry( char const* pzName, tOptionValue* pRes ); + +static void +unloadNestedArglist( tArgList* pAL ); + +static void +sortNestedList( tArgList* pAL ); +/* = = = END-STATIC-FORWARD = = = */ + +/* removeBackslashes + * + * This function assumes that all newline characters were preceeded by + * backslashes that need removal. + */ +static void +removeBackslashes( char* pzSrc ) +{ + char* pzD = strchr(pzSrc, '\n'); + + if (pzD == NULL) + return; + *--pzD = '\n'; + + for (;;) { + char ch = ((*pzD++) = *(pzSrc++)); + switch (ch) { + case '\n': *--pzD = ch; break; + case NUL: return; + default: + ; + } + } +} + + +/* scanQuotedString + * + * Find the end of a quoted string, skipping escaped quote characters. + */ +static char const* +scanQuotedString( char const* pzTxt ) +{ + char q = *(pzTxt++); /* remember the type of quote */ + + for (;;) { + char ch = *(pzTxt++); + if (ch == NUL) + return pzTxt-1; + + if (ch == q) + return pzTxt; + + if (ch == '\\') { + ch = *(pzTxt++); + /* + * IF the next character is NUL, drop the backslash, too. + */ + if (ch == NUL) + return pzTxt - 2; + + /* + * IF the quote character or the escape character were escaped, + * then skip both, as long as the string does not end. + */ + if ((ch == q) || (ch == '\\')) { + if (*(pzTxt++) == NUL) + return pzTxt-1; + } + } + } +} + + +/* addStringValue + * + * Associate a name with either a string or no value. + */ +static tOptionValue* +addStringValue( void** pp, char const* pzName, size_t nameLen, + char const* pzValue, size_t dataLen ) +{ + tOptionValue* pNV; + size_t sz = nameLen + dataLen + sizeof(*pNV); + + pNV = AGALOC( sz, "option name/str value pair" ); + if (pNV == NULL) + return NULL; + + if (pzValue == NULL) { + pNV->valType = OPARG_TYPE_NONE; + pNV->pzName = pNV->v.strVal; + + } else { + pNV->valType = OPARG_TYPE_STRING; + if (dataLen > 0) + memcpy( pNV->v.strVal, pzValue, dataLen ); + pNV->v.strVal[dataLen] = NUL; + pNV->pzName = pNV->v.strVal + dataLen + 1; + } + + memcpy( pNV->pzName, pzName, nameLen ); + pNV->pzName[ nameLen ] = NUL; + addArgListEntry( pp, pNV ); + return pNV; +} + + +/* addBoolValue + * + * Associate a name with either a string or no value. + */ +static tOptionValue* +addBoolValue( void** pp, char const* pzName, size_t nameLen, + char const* pzValue, size_t dataLen ) +{ + tOptionValue* pNV; + size_t sz = nameLen + sizeof(*pNV) + 1; + + pNV = AGALOC( sz, "option name/bool value pair" ); + if (pNV == NULL) + return NULL; + while (isspace( (int)*pzValue ) && (dataLen > 0)) { + dataLen--; pzValue++; + } + if (dataLen == 0) + pNV->v.boolVal = 0; + else if (isdigit( (int)*pzValue )) + pNV->v.boolVal = atoi( pzValue ); + else switch (*pzValue) { + case 'f': + case 'F': + case 'n': + case 'N': + pNV->v.boolVal = 0; break; + default: + pNV->v.boolVal = 1; + } + + pNV->valType = OPARG_TYPE_BOOLEAN; + pNV->pzName = (char*)(pNV + 1); + memcpy( pNV->pzName, pzName, nameLen ); + pNV->pzName[ nameLen ] = NUL; + addArgListEntry( pp, pNV ); + return pNV; +} + + +/* addNumberValue + * + * Associate a name with either a string or no value. + */ +static tOptionValue* +addNumberValue( void** pp, char const* pzName, size_t nameLen, + char const* pzValue, size_t dataLen ) +{ + tOptionValue* pNV; + size_t sz = nameLen + sizeof(*pNV) + 1; + + pNV = AGALOC( sz, "option name/bool value pair" ); + if (pNV == NULL) + return NULL; + while (isspace( (int)*pzValue ) && (dataLen > 0)) { + dataLen--; pzValue++; + } + if (dataLen == 0) + pNV->v.boolVal = 0; + else + pNV->v.boolVal = atoi( pzValue ); + + pNV->valType = OPARG_TYPE_NUMERIC; + pNV->pzName = (char*)(pNV + 1); + memcpy( pNV->pzName, pzName, nameLen ); + pNV->pzName[ nameLen ] = NUL; + addArgListEntry( pp, pNV ); + return pNV; +} + + +/* addNestedValue + * + * Associate a name with either a string or no value. + */ +static tOptionValue* +addNestedValue( void** pp, char const* pzName, size_t nameLen, + char* pzValue, size_t dataLen ) +{ + tOptionValue* pNV; + + if (dataLen == 0) { + size_t sz = nameLen + sizeof(*pNV) + 1; + pNV = AGALOC( sz, "empty nested value pair" ); + if (pNV == NULL) + return NULL; + pNV->v.nestVal = NULL; + pNV->valType = OPARG_TYPE_HIERARCHY; + pNV->pzName = (char*)(pNV + 1); + memcpy( pNV->pzName, pzName, nameLen ); + pNV->pzName[ nameLen ] = NUL; + + } else { + pNV = optionLoadNested( pzValue, pzName, nameLen ); + } + + if (pNV != NULL) + addArgListEntry( pp, pNV ); + + return pNV; +} + + +/* scanNameEntry + * + * We have an entry that starts with a name. Find the end of it, cook it + * (if called for) and create the name/value association. + */ +static char const* +scanNameEntry(char const* pzName, tOptionValue* pRes) +{ + tOptionValue* pNV; + char const * pzScan = pzName+1; + char const * pzVal; + size_t nameLen = 1; + size_t dataLen = 0; + + while (ISNAMECHAR( (int)*pzScan )) { pzScan++; nameLen++; } + + while (isspace( (int)*pzScan )) { + char ch = *(pzScan++); + if ((ch == '\n') || (ch == ',')) { + addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL,(size_t)0); + return pzScan - 1; + } + } + + switch (*pzScan) { + case '=': + case ':': + while (isspace( (int)*++pzScan )) ; + switch (*pzScan) { + case ',': goto comma_char; + case '"': + case '\'': goto quote_char; + case NUL: goto nul_byte; + default: goto default_char; + } + + case ',': + comma_char: + pzScan++; + /* FALLTHROUGH */ + + case NUL: + nul_byte: + addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0); + break; + + case '"': + case '\'': + quote_char: + pzVal = pzScan; + pzScan = scanQuotedString( pzScan ); + dataLen = pzScan - pzVal; + pNV = addStringValue( &(pRes->v.nestVal), pzName, nameLen, pzVal, + dataLen ); + if ((pNV != NULL) && (option_load_mode == OPTION_LOAD_COOKED)) + ao_string_cook( pNV->v.strVal, NULL ); + break; + + default: + default_char: + /* + * We have found some strange text value. It ends with a newline + * or a comma. + */ + pzVal = pzScan; + for (;;) { + char ch = *(pzScan++); + switch (ch) { + case NUL: + pzScan--; + dataLen = pzScan - pzVal; + goto string_done; + /* FALLTHROUGH */ + + case '\n': + if ( (pzScan > pzVal + 2) + && (pzScan[-2] == '\\') + && (pzScan[ 0] != NUL)) + continue; + /* FALLTHROUGH */ + + case ',': + dataLen = (pzScan - pzVal) - 1; + string_done: + pNV = addStringValue( &(pRes->v.nestVal), pzName, nameLen, + pzVal, dataLen ); + if (pNV != NULL) + removeBackslashes( pNV->v.strVal ); + goto leave_scan_name; + } + } + break; + } leave_scan_name:; + + return pzScan; +} + + +/* scanXmlEntry + * + * We've found a '<' character. We ignore this if it is a comment or a + * directive. If it is something else, then whatever it is we are looking + * at is bogus. Returning NULL stops processing. + */ +static char const* +scanXmlEntry( char const* pzName, tOptionValue* pRes ) +{ + size_t nameLen = 1, valLen = 0; + char const* pzScan = ++pzName; + char const* pzVal; + tOptionValue valu; + tOptionValue* pNewVal; + tOptionLoadMode save_mode = option_load_mode; + + if (! isalpha((int)*pzName)) { + switch (*pzName) { + default: + pzName = NULL; + break; + + case '!': + pzName = strstr( pzName, "-->" ); + if (pzName != NULL) + pzName += 3; + break; + + case '?': + pzName = strchr( pzName, '>' ); + if (pzName != NULL) + pzName++; + break; + } + return pzName; + } + + while (isalpha( (int)*++pzScan )) nameLen++; + if (nameLen > 64) + return NULL; + valu.valType = OPARG_TYPE_STRING; + + switch (*pzScan) { + case ' ': + case '\t': + pzScan = parseAttributes( + NULL, (char*)pzScan, &option_load_mode, &valu ); + if (*pzScan == '>') { + pzScan++; + break; + } + + if (*pzScan != '/') { + option_load_mode = save_mode; + return NULL; + } + /* FALLTHROUGH */ + + case '/': + if (*++pzScan != '>') { + option_load_mode = save_mode; + return NULL; + } + addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0); + option_load_mode = save_mode; + return pzScan+2; + + default: + option_load_mode = save_mode; + return NULL; + + case '>': + pzScan++; + break; + } + + pzVal = pzScan; + + { + char z[68]; + char* pzD = z; + int ct = nameLen; + char const* pzS = pzName; + + *(pzD++) = '<'; + *(pzD++) = '/'; + + do { + *(pzD++) = *(pzS++); + } while (--ct > 0); + *(pzD++) = '>'; + *pzD = NUL; + + pzScan = strstr( pzScan, z ); + if (pzScan == NULL) { + option_load_mode = save_mode; + return NULL; + } + valLen = (pzScan - pzVal); + pzScan += nameLen + 3; + while (isspace( (int)*pzScan )) pzScan++; + } + + switch (valu.valType) { + case OPARG_TYPE_NONE: + addStringValue( &(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0); + break; + + case OPARG_TYPE_STRING: + pNewVal = addStringValue( + &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen); + + if (option_load_mode == OPTION_LOAD_KEEP) + break; + mungeString( pNewVal->v.strVal, option_load_mode ); + break; + + case OPARG_TYPE_BOOLEAN: + addBoolValue( &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen ); + break; + + case OPARG_TYPE_NUMERIC: + addNumberValue( &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen ); + break; + + case OPARG_TYPE_HIERARCHY: + { + char* pz = AGALOC( valLen+1, "hierarchical scan" ); + if (pz == NULL) + break; + memcpy( pz, pzVal, valLen ); + pz[valLen] = NUL; + addNestedValue( &(pRes->v.nestVal), pzName, nameLen, pz, valLen ); + AGFREE(pz); + break; + } + + case OPARG_TYPE_ENUMERATION: + case OPARG_TYPE_MEMBERSHIP: + default: + break; + } + + option_load_mode = save_mode; + return pzScan; +} + + +/* unloadNestedArglist + * + * Deallocate a list of option arguments. This must have been gotten from + * a hierarchical option argument, not a stacked list of strings. It is + * an internal call, so it is not validated. The caller is responsible for + * knowing what they are doing. + */ +static void +unloadNestedArglist( tArgList* pAL ) +{ + int ct = pAL->useCt; + tCC** ppNV = pAL->apzArgs; + + while (ct-- > 0) { + tOptionValue* pNV = (tOptionValue*)(void*)*(ppNV++); + if (pNV->valType == OPARG_TYPE_HIERARCHY) + unloadNestedArglist( pNV->v.nestVal ); + AGFREE( pNV ); + } + + AGFREE( (void*)pAL ); +} + + +/*=export_func optionUnloadNested + * + * what: Deallocate the memory for a nested value + * arg: + tOptionValue const * + pOptVal + the hierarchical value + + * + * doc: + * A nested value needs to be deallocated. The pointer passed in should + * have been gotten from a call to @code{configFileLoad()} (See + * @pxref{libopts-configFileLoad}). +=*/ +void +optionUnloadNested( tOptionValue const * pOV ) +{ + if (pOV == NULL) return; + if (pOV->valType != OPARG_TYPE_HIERARCHY) { + errno = EINVAL; + return; + } + + unloadNestedArglist( pOV->v.nestVal ); + + AGFREE( (void*)pOV ); +} + + +/* sortNestedList + * + * This is a _stable_ sort. The entries are sorted alphabetically, + * but within entries of the same name the ordering is unchanged. + * Typically, we also hope the input is sorted. + */ +static void +sortNestedList( tArgList* pAL ) +{ + int ix; + int lm = pAL->useCt; + + /* + * This loop iterates "useCt" - 1 times. + */ + for (ix = 0; ++ix < lm;) { + int iy = ix-1; + tOptionValue* pNewNV = (tOptionValue*)(void*)(pAL->apzArgs[ix]); + tOptionValue* pOldNV = (tOptionValue*)(void*)(pAL->apzArgs[iy]); + + /* + * For as long as the new entry precedes the "old" entry, + * move the old pointer. Stop before trying to extract the + * "-1" entry. + */ + while (strcmp( pOldNV->pzName, pNewNV->pzName ) > 0) { + pAL->apzArgs[iy+1] = (void*)pOldNV; + pOldNV = (tOptionValue*)(void*)(pAL->apzArgs[--iy]); + if (iy < 0) + break; + } + + /* + * Always store the pointer. Sometimes it is redundant, + * but the redundancy is cheaper than a test and branch sequence. + */ + pAL->apzArgs[iy+1] = (void*)pNewNV; + } +} + + +/* optionLoadNested + * private: + * + * what: parse a hierarchical option argument + * arg: + char const* + pzTxt + the text to scan + + * arg: + char const* + pzName + the name for the text + + * arg: + size_t + nameLen + the length of "name" + + * + * ret_type: tOptionValue* + * ret_desc: An allocated, compound value structure + * + * doc: + * A block of text represents a series of values. It may be an + * entire configuration file, or it may be an argument to an + * option that takes a hierarchical value. + */ +LOCAL tOptionValue* +optionLoadNested(char const* pzTxt, char const* pzName, size_t nameLen) +{ + tOptionValue* pRes; + tArgList* pAL; + + /* + * Make sure we have some data and we have space to put what we find. + */ + if (pzTxt == NULL) { + errno = EINVAL; + return NULL; + } + while (isspace( (int)*pzTxt )) pzTxt++; + if (*pzTxt == NUL) { + errno = ENOENT; + return NULL; + } + pRes = AGALOC( sizeof(*pRes) + nameLen + 1, "nested args" ); + if (pRes == NULL) { + errno = ENOMEM; + return NULL; + } + pRes->valType = OPARG_TYPE_HIERARCHY; + pRes->pzName = (char*)(pRes + 1); + memcpy( pRes->pzName, pzName, nameLen ); + pRes->pzName[ nameLen ] = NUL; + + pAL = AGALOC( sizeof(*pAL), "nested arg list" ); + if (pAL == NULL) { + AGFREE( pRes ); + return NULL; + } + pRes->v.nestVal = pAL; + pAL->useCt = 0; + pAL->allocCt = MIN_ARG_ALLOC_CT; + + /* + * Scan until we hit a NUL. + */ + do { + while (isspace( (int)*pzTxt )) pzTxt++; + if (isalpha( (int)*pzTxt )) { + pzTxt = scanNameEntry( pzTxt, pRes ); + } + else switch (*pzTxt) { + case NUL: goto scan_done; + case '<': pzTxt = scanXmlEntry( pzTxt, pRes ); + if (*pzTxt == ',') pzTxt++; break; + case '#': pzTxt = strchr( pzTxt, '\n' ); break; + default: goto woops; + } + } while (pzTxt != NULL); scan_done:; + + pAL = pRes->v.nestVal; + if (pAL->useCt != 0) { + sortNestedList( pAL ); + return pRes; + } + + woops: + AGFREE( pRes->v.nestVal ); + AGFREE( pRes ); + return NULL; +} + + +/*=export_func optionNestedVal + * private: + * + * what: parse a hierarchical option argument + * arg: + tOptions* + pOpts + program options descriptor + + * arg: + tOptDesc* + pOptDesc + the descriptor for this arg + + * + * doc: + * Nested value was found on the command line +=*/ +void +optionNestedVal( tOptions* pOpts, tOptDesc* pOD ) +{ + tOptionValue* pOV = optionLoadNested( + pOD->optArg.argString, pOD->pz_Name, strlen(pOD->pz_Name)); + + if (pOV != NULL) + addArgListEntry( &(pOD->optCookie), (void*)pOV ); +} +/* + * Local Variables: + * mode: C + * c-file-style: "stroustrup" + * indent-tabs-mode: nil + * End: + * end of autoopts/nested.c */ |