ban2fail/cfgmap.c

1323 lines
34 KiB
C

/***************************************************************************
* Copyright (C) 2019 by John D. Robertson *
* john@rrci.com *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program 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 *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <syslog.h>
#include <libgen.h>
#include <assert.h>
#include "cfgmap.h"
#include "ptrvec.h"
#include "util.h"
/* Internal classes */
typedef struct _VALUE {
char *str;
unsigned serial_no;
} VALUE;
/* Forward declarations */
static CFGMAP_ENTRY* CFGMAP_ENTRY_constructor(CFGMAP_ENTRY * self,
const char *symbol,
unsigned symLen);
static int _CFGMAP_fh_read(CFGMAP * self, FILE * fh, const char *fname);
static int cmp_symbol(const void *v1, const void *v2);
static int count_tuples(void *item_ptr, void *data);
static int str_extract_string(char **rtnVal, const char **pStr);
static int unused_load_arr(void *item_ptr, void *data);
static VALUE* CFGMAP_ENTRY_append_value(CFGMAP_ENTRY * self,
const char *val_str,
unsigned serial_no);
static VALUE* VALUE_constructor(VALUE * self, const char *str, unsigned serial_no);
static void* CFGMAP_ENTRY_destructor(CFGMAP_ENTRY * self);
static void* VALUE_destructor(VALUE * self);
/* Internal macros */
#define VALUE_create(p, str, serial_no) \
((p)= (VALUE_constructor((p)=malloc(sizeof(VALUE)), str, serial_no) ? (p) : ( p ? realloc(VALUE_destructor(p),0) : 0 )))
#define VALUE_destroy(s) \
{if(VALUE_destructor(s)) {free(s); s=NULL;}}
#define CFGMAP_ENTRY_create(p, sym, symLen) \
(CFGMAP_ENTRY_constructor((p)=malloc(sizeof(CFGMAP_ENTRY)), sym, symLen) ? (p) : ( p ? realloc(CFGMAP_ENTRY_destructor(p),0) : 0 ))
#define CFGMAP_ENTRY_destroy(s) \
if(CFGMAP_ENTRY_destructor(s)) {free(s);}
/*===========================================================================================*/
/*================================== CFGMAP ===============================================*/
/*===========================================================================================*/
const CFGMAP_ENTRY*
CFGMAP_find(CFGMAP * self, const char *symbol)
/************************************************************************
* Find entry for the given null terminated symbol.
*/
{
CFGMAP_ENTRY *rtn =
MAP_findItem(&self->entry_map, symbol, strlen(symbol));
if (rtn)
++rtn->nLookups;
return rtn;
}
const char*
CFGMAP_find_single_value(CFGMAP * self, const char *symbol)
/************************************************************************
* Find exactly one value for a symbol, or return NULL.
*/
{
const CFGMAP_ENTRY *pEntry;
unsigned numVals;
if (!(pEntry = CFGMAP_find(self, symbol)))
return NULL;
if ((numVals = CFGMAP_ENTRY_numValues(pEntry)) != 1) {
eprintf("ERROR: \"%s\" has %u values instead of 1.", symbol, numVals);
return NULL;
}
return CFGMAP_ENTRY_value(pEntry, 0);
}
const char*
CFGMAP_find_last_value(CFGMAP * self, const char *symbol)
/************************************************************************
* Find the last value for a symbol, or return NULL.
*/
{
const CFGMAP_ENTRY *pEntry;
unsigned numVals;
if (!(pEntry = CFGMAP_find(self, symbol)))
return NULL;
numVals = CFGMAP_ENTRY_numValues(pEntry);
return CFGMAP_ENTRY_value(pEntry, numVals - 1);
}
int
CFGMAP_append(CFGMAP * self, const char *symbol, unsigned int symLen,
const char *value)
/**************************************************************************
* Append a value to a config entry, creating a new entry if necessary.
*/
{
CFGMAP_ENTRY *pEntry;
VALUE *v;
/* Create a new CFGMAP_ENTRY if no match is found. */
if (!(pEntry = MAP_findItem(&self->entry_map, symbol, symLen))) {
if (!CFGMAP_ENTRY_create(pEntry, symbol, symLen))
return 1;
if (MAP_addKey
(&self->entry_map, pEntry->symbol, pEntry->symLen, pEntry))
assert(0);
}
/* Return now if there is no value to remember. */
if (!strlen(value))
return 0;
/* Must recount tuples */
self->flags &= ~CFGMAP_NTUPLES_KNOWN_FLG;
/* Otherwise, add the value to the list */
if (!CFGMAP_ENTRY_append_value(pEntry, value, ++self->serial_no_seq))
assert(0);
return 0;
}
CFGMAP*
CFGMAP_file_constructor(CFGMAP * self, const char *fname)
/******************************************************************
* Create a map populated from the supplied file.
*/
{
if (!(CFGMAP_constructor(self)))
return NULL;
if (CFGMAP_file_read(self, fname))
return NULL;
return self;
}
CFGMAP*
CFGMAP_constructor(CFGMAP * self)
/******************************************************************
* Create an empty map.
*/
{
if (!self)
return NULL;
memset(self, 0, sizeof(*self));
/* Initialize data structures */
if (!MAP_constructor(&self->entry_map, 30, 100) ||
!PTRVEC_constructor(&self->curlyBlock_lst, 10))
return NULL;
return self;
}
void*
CFGMAP_destructor(CFGMAP * self)
/******************************************************************
* Free resources associated with map.
*/
{
/* Remove configuration map entries */
MAP_clearAndDestroy(&self->entry_map,
(void *(*)(void *))CFGMAP_ENTRY_destructor);
MAP_destructor(&self->entry_map);
{ /* Remove curly brace block symbols list */
char *str;
while ((str = PTRVEC_remHead(&self->curlyBlock_lst))) {
free(str);
}
}
PTRVEC_destructor(&self->curlyBlock_lst);
return self;
}
static int
_CFGMAP_fh_read(CFGMAP * self, FILE * fh, const char *fname)
/**************************************************************************
* Read a file to populate the map.
*/
{
int rtn = 1;
unsigned lineNo;
char cwd[PATH_MAX];
/* Note the change in recursion level */
++self->recurs_lvl;
{ /* get a copy of the current directory */
char tmp_fname[PATH_MAX];
strncpy(tmp_fname, fname, sizeof(tmp_fname) - 1);
tmp_fname[sizeof(tmp_fname) - 1] = '\0';
strncpy(cwd, dirname(tmp_fname), sizeof(cwd) - 1);
cwd[sizeof(cwd) - 1] = '\0';
}
#ifdef DDEBUG
eprintf("INFO: %s(\"%s\")", __FUNCTION__, fname);
#endif
{ /* Parse the file, build a map */
char lineBuf[1024];
unsigned int charNo, currLen;
enum {
COMMENT_STATE = 1 << 0,
STRING_STATE = 1 << 1,
CURLY_BEGIN_STATE = 1 << 2,
REPLACE_STATE = 1 << 3,
NDX_STATE = 1 << 4,
ESCAPED_STATE = 1 << 5
} stateFlags;
for (lineNo = 1, charNo = 1, currLen = 0, stateFlags = 0;; ++charNo) {
char *str;
int c;
/* Check for buffer overflow */
if (currLen + 1 == sizeof(lineBuf)) {
eprintf("ERROR: line buffer overflow at line %u.", lineNo);
goto abort;
}
/* Grab the next character from the file */
c = getc(fh);
/* End of current line? */
if (c == '\n' || c == EOF) {
char substBuf[sizeof(lineBuf)];
// TODO: here is where a continuation backslash could be checked for
if (stateFlags & STRING_STATE) {
eprintf("ERROR: Unterminated string in \"%s\" at line %u.",
fname, lineNo);
goto abort;
}
/* Null terminate buffer */
lineBuf[currLen] = '\0';
/* Skip leading whitespace */
/* Get rid of trailing whitespace */
str= trim(lineBuf);
{ /*--- Perform substitutions ---*/
unsigned nCopied;
/* Null terminate substituted string. */
substBuf[sizeof(substBuf) - 1] = '\0';
/* Perform substitutions */
for (nCopied = 0; *str && nCopied + 1 < sizeof(substBuf);) {
if (!strncmp(str, "$CWD", 4)) {
strncpy(substBuf + nCopied, cwd,
sizeof(substBuf) - nCopied - 1);
str += 4;
nCopied += strlen(substBuf + nCopied);
} else {
substBuf[nCopied] = *str;
++str;
++nCopied;
}
}
/* Make sure the substBuf is null terminated */
substBuf[nCopied] = '\0';
/* Further work done with contents of substitution buffer */
currLen = nCopied;
str = substBuf;
}
/* Process lines with non-zero length */
if (*str) {
assert(currLen);
/* Last character issues */
switch (str[currLen - 1]) {
case '{':
stateFlags |= CURLY_BEGIN_STATE;
/* Get rid of trailing whitespace */
str[--currLen] = '\0';
trimend(str);
break;
case '}':
{
char *entry = PTRVEC_remTail(&self->curlyBlock_lst);
if (!entry) {
eprintf
("ERROR: Unmatched '}' found in \"%s\" at line %u.",
fname, lineNo);
goto abort;
}
free(entry);
}
if (currLen == 1)
goto doneProcessing;
break;
}
if (!strncmp(str, ".include", 8)) {
char *fname;
str += 8;
#pragma GCC diagnostic ignored "-Wcast-qual"
if (str_extract_string(&fname, (const char **)(&str)))
assert(0);
/* Call our self to continue */
if (CFGMAP_file_read(self, fname)) {
eprintf("ERROR: failed at line %u.", lineNo);
goto abort;
}
free(fname);
goto doneProcessing;
} else if (!strncmp(str, ".shell", 6)) {
char *cmd;
FILE *pofh = NULL;
cmd = skipspace(str + 6);
if (!(pofh = popen(cmd, "r")) ||
_CFGMAP_fh_read(self, pofh, fname)) {
eprintf("ERROR: \".shell %s\" failed at line %u.", cmd,
lineNo);
goto abort;
}
if (pofh)
pclose(pofh);
goto doneProcessing;
} else if (*str == '@') {
/* Skip leading whitespace */
for (++str, --currLen; *str && isspace(*str); ++str, --currLen) ;
stateFlags |= REPLACE_STATE;
} else if (*str == '}') { /* Ending of curly brace block */
char *entry = PTRVEC_remTail(&self->curlyBlock_lst);
if (!entry) {
eprintf("ERROR: Unmatched '}' found in \"%s\" at line %u.",
fname, lineNo);
goto abort;
}
free(entry);
goto doneProcessing;
}
{ /* At this point we hope we have a symbol+value pair */
size_t symLen;
unsigned ndx;
char *valStr, *ndxStr, symBuf[sizeof(lineBuf) * 2];
/* Find the length of the symbol */
symLen = strcspn(str, " \t=[");
/* Default to end of symbol */
valStr = str + symLen;
/* If we have a value, find the beginning */
if (*valStr) {
/* Skip whitespace */
valStr= skipspace(valStr);
if (*valStr == '[') {
int n;
if (sscanf(valStr, "[ %u ]%n", &ndx, &n) < 1) {
eprintf
("ERROR: array notation in .replace without a value in \"%s\" at line %u.",
fname, lineNo);
goto abort;
}
valStr += n;
stateFlags |= NDX_STATE;
/* Skip whitespace */
valStr= skipspace(valStr);
}
if (*valStr == '=')
++valStr;
/* Skip remaining whitespace */
valStr= skipspace(valStr);
}
/* Generate symbol prefix, if necessary */
if (PTRVEC_numItems(&self->curlyBlock_lst)) {
size_t len;
symBuf[0] = '\0';
{ /* Prefix segments from ancestor curly brace blocks */
unsigned i;
const char *pfix;
PTRVEC_loopFwd(&self->curlyBlock_lst, i, pfix) {
strncat(symBuf, "\\", sizeof(symBuf) - 1);
strncat(symBuf, pfix, sizeof(symBuf) - 1);
}
}
/* Leading period before current symbol */
strncat(symBuf, "\\", sizeof(symBuf) - 1);
/* Tag on the current symbol */
len = strlen(symBuf);
if (len + symLen >= sizeof(symBuf)) {
eprintf
("ERROR: symbol buffer overrun in \"%s\" at line %u.",
fname, lineNo);
goto abort;
}
strncat(symBuf + len, str, symLen);
symLen += len;
} else {
assert(symLen + 1 < sizeof(symBuf));
/* Copy into symBuf. */
symBuf[0] = '\\';
memcpy(symBuf + 1, str, symLen);
++symLen;
}
/* Null terminate symBuf */
symBuf[symLen] = '\0';
if (stateFlags & REPLACE_STATE) { /* Need to replace an existing entry */
unsigned nVals;
/* We have to skip over the default leading '\' in symBuf, because it was supplied in the config file. */
CFGMAP_ENTRY *pEntry =
MAP_findItem(&self->entry_map, symBuf + 1,
symLen - 1);
if (!pEntry) {
eprintf
("ERROR: No symbol \"%s\" to replace in \"%s\" at line %u.",
symBuf + 1, fname, lineNo);
goto abort;
}
nVals = CFGMAP_ENTRY_numValues(pEntry);
if (stateFlags & NDX_STATE) { /* Entry is indexed */
unsigned i;
VALUE *val, *valArr[nVals];
if (ndx >= nVals) {
eprintf
("ERROR: .replace ndx= %u out of range in \"%s\" at line %u.",
ndx, fname, lineNo);
goto abort;
}
/* Load value VALUES into temporary array */
PTRVEC_loopFwd(&pEntry->value_lst, i, val) {
if (ndx == i) { /* Replace this value */
/* Preserve the serial number */
unsigned sn = val->serial_no;
VALUE_destroy(val);
VALUE_create(valArr[i], valStr, sn);
assert(valArr[i]);
} else { /* Copy existing value */
valArr[i] = val;
}
}
/* Put temporary array back into PTRVEC */
PTRVEC_reset(&pEntry->value_lst);
for (i = 0; i < nVals; ++i) {
PTRVEC_addTail(&pEntry->value_lst, valArr[i]);
}
} else { /* Must be only one entry */
if (nVals != 1) {
eprintf
("ERROR: Non-indexed .replace with ambiguous %u value target in \"%s\" at line %u.",
nVals, fname, lineNo);
goto abort;
}
free(PTRVEC_remHead(&pEntry->value_lst));
PTRVEC_addTail(&pEntry->value_lst, strdup(valStr));
}
} else {
assert(!(stateFlags & NDX_STATE));
/* Add the symbol+value pair to the memory based map */
if (CFGMAP_append(self, symBuf, symLen, valStr)) {
assert(0);
}
/* Handle the case where a curly brace block begins */
if (stateFlags & CURLY_BEGIN_STATE) {
PTRVEC_addTail(&self->curlyBlock_lst, strdup(valStr));
}
}
} /* symbol + value pair */
doneProcessing:
;
}
/* Non-empty string */
/* All done? */
if (c == EOF)
break;
/* Get ready for the next line */
++lineNo;
currLen = 0;
charNo = 0;
stateFlags = 0;
}
/* EOL or EOF */
/* Ignore other non-printing characters */
if (!isprint(c)) {
continue;
}
/* Handle escaped characters */
if (c == '\\' &&
!(stateFlags & COMMENT_STATE) && !(stateFlags & ESCAPED_STATE)) {
stateFlags |= ESCAPED_STATE;
continue;
}
/* Handle the different states of the "state machine" */
if (stateFlags & COMMENT_STATE) { /* In the middle of a comment */
/* Ignore character */
continue;
} else if (stateFlags & STRING_STATE) { /* In the middle of a quoted string */
switch (c) {
case '"':
if (!(stateFlags & ESCAPED_STATE)) {
stateFlags &= ~STRING_STATE;
}
break;
}
/* valid character */
lineBuf[currLen] = c;
++currLen;
} else { /* Currently no special state */
switch (c) {
case '#': /* Ignore rest of line */
if (!(stateFlags & ESCAPED_STATE)) {
stateFlags |= COMMENT_STATE;
continue;
}
break;
case '"': /* This is a string which may contain a # character */
if (!(stateFlags & ESCAPED_STATE)) {
stateFlags |= STRING_STATE;
}
break;
default:
if (stateFlags & ESCAPED_STATE) {
lineBuf[currLen] = '\\';
++currLen;
}
}
/* This is a valid character */
lineBuf[currLen] = c;
++currLen;
} /* Currently no special state */
stateFlags &= ~ESCAPED_STATE;
} /* character by character for loop */
} /* Parse file scope */
/* Note the change in recursion level */
--self->recurs_lvl;
/* Check for unmatched curly braces in the outermost file */
if (!self->recurs_lvl && PTRVEC_numItems(&self->curlyBlock_lst)) {
eprintf("ERROR: Unterminated curly brace in \"%s\" at line %u.", fname,
lineNo);
goto abort;
}
rtn = 0;
abort:
return rtn;
}
int
CFGMAP_file_read(CFGMAP * self, const char *fname)
/**************************************************************************
* Read a file to populate the map.
*/
{
int rtn = 1;
FILE *fh = NULL;
#ifdef DDEBUG
eprintf("INFO: %s(\"%s\")", __FUNCTION__, fname);
#endif
/* Open the supplied file for reading */
if (!(fh = fopen(fname, "r"))) {
eprintf("ERROR: cannot open \"%s\"", fname);
goto abort;
}
if (_CFGMAP_fh_read(self, fh, fname))
goto abort;
rtn = 0;
abort:
if (fh)
fclose(fh);
return rtn;
}
int
CFGMAP_query_uint(CFGMAP * self, unsigned int *pRtn, unsigned int dfltVal, const char *symbol)
/**********************************************************************************
* Convenience query function.
*/
{
const char *valStr;
/* Initialize with default value */
*pRtn = dfltVal;
if (!(valStr = CFGMAP_find_single_value(self, symbol)))
return 0;
if (sscanf(valStr, "%u", pRtn) != 1) {
eprintf("ERROR: \"%s = %s\" is not a valid unsigned integer specifier.",
symbol, valStr);
return 1;
}
return 0;
}
int
CFGMAP_query_last_flags(
CFGMAP * self,
int *pRtn,
int dfltVal,
const char *symbol,
const struct bitTuple bt_arr[]
)
/**********************************************************************************
* Convenience query function for possibly OR'd flags. bt_arr[] is terminated with a NULL value
* in enumStr.
*/
{
const char *valStr, *str;
size_t flagLen;
// TODO: implement all bit operators (e.g. '|', '~', '<<', '>>')
/* Initialize with default value */
*pRtn = dfltVal;
/* See if any corresponding entries exist in the map */
if (!(valStr = CFGMAP_find_last_value(self, symbol)))
return 0;
int64_t rtnBuf;
if(-1 == str2bits(&rtnBuf, valStr, bt_arr)) {
eprintf("ERROR: \"%s\" is not a valid flag.", symbol, valStr);
return 1;
}
*pRtn= rtnBuf;
return 0;
}
int
CFGMAP_query_last_enum(
CFGMAP * self,
int *pRtn,
int dfltVal,
const char *symbol,
const struct enumTuple et_arr[]
)
/**********************************************************************************
* Convenience query function for enums. et_arr[] is terminated with a NULL value
* in enumStr.
*/
{
const char *valStr;
unsigned i;
/* Initialize with default value */
*pRtn = dfltVal;
/* See if any corresponding entries exist in the map */
if (!(valStr = CFGMAP_find_last_value(self, symbol)))
return 0;
const struct enumTuple *et= str2enum(valStr, et_arr);
/* Assign enum value, if found */
if(et) {
*pRtn= et->enumVal;
return 0;
}
return 0;
}
int
CFGMAP_query_last_bool(CFGMAP * self,
int *pRtn, int dfltVal, const char *symbol)
/**********************************************************************************
* Convenience function.
*/
{
const char *val;
*pRtn = dfltVal;
if (!(val = CFGMAP_find_last_value(self, symbol)))
return 0;
if (!strcmp(val, "yes") || !strcmp(val, "true") || !strcmp(val, "1")) {
*pRtn = 1;
return 0;
}
if (!strcmp(val, "no") || !strcmp(val, "false") || !strcmp(val, "0")) {
*pRtn = 0;
return 0;
}
eprintf("ERROR: \"%s = %s\" is not a valid boolean specifier.", symbol, val);
return 1;
}
int
CFGMAP_query_last_int(CFGMAP * self, int *pRtn, int dfltVal,
const char *symbol)
/**********************************************************************************
* Convenience function.
*/
{
const char *val;
*pRtn = dfltVal;
if (!(val = CFGMAP_find_last_value(self, symbol)))
return 0;
if (sscanf(val, "%d", pRtn) != 1) {
eprintf("ERROR: \"%s = %s\" is not a valid integer specification.",
symbol, val);
return 1;
}
return 0;
}
int
CFGMAP_query_last_uint(CFGMAP * self, unsigned *pRtn, unsigned dfltVal,
const char *symbol)
/**********************************************************************************
* Convenience function.
*/
{
const char *val;
*pRtn = dfltVal;
if (!(val = CFGMAP_find_last_value(self, symbol)))
return 0;
if (sscanf(val, "%u", pRtn) != 1) {
eprintf
("ERROR: \"%s = %s\" is not a valid unsigned integer specification.",
symbol, val);
return 1;
}
return 0;
}
int
CFGMAP_query_last_dbl(CFGMAP * self, double *pRtn, double dfltVal,
const char *symbol)
/**********************************************************************************
* Convenience function.
*/
{
const char *val;
*pRtn = dfltVal;
if (!(val = CFGMAP_find_last_value(self, symbol)))
return 0;
if (sscanf(val, "%lf", pRtn) != 1) {
eprintf("ERROR: \"%s = %s\" is not a valid floating point specification.",
symbol, val);
return 1;
}
return 0;
}
int
CFGMAP_query_last_string(CFGMAP * self, char **pRtn, const char *dfltVal,
const char *symbol)
/**********************************************************************************
* Convenience function.
* Note: string assigned to *pRtn was allocated by strdup().
*/
{
const char *val;
if (!(val = CFGMAP_find_last_value(self, symbol))) {
if (dfltVal) {
*pRtn = strdup(dfltVal);
assert(*pRtn);
} else {
/* NULL is a valid dfltVal */
*pRtn = NULL;
}
return 0;
}
return str_extract_string(pRtn, &val);
}
int
CFGMAP_query_last_time_of_day(CFGMAP * self, int *pRtn, int dfltVal,
const char *symbol)
/**********************************************************************************
* Convenience function to convert hh:mm to a number of seconds.
*/
{
const char *val;
int hr, min;
*pRtn = dfltVal;
if (!(val = CFGMAP_find_last_value(self, symbol)))
return 0;
if (sscanf(val, "%u:%u", &hr, &min) != 2) {
eprintf("ERROR: \"%s = %s\" is not a valid time of day specification.",
symbol, val);
return 1;
}
/* Convert hours and minutes into seconds */
*pRtn = 3600 * hr + 60 * min;
return 0;
}
static int
cmp_symbol(const void *v1, const void *v2)
/******************************************************************
* lambda function for qsort() puts entries in case insensitive,
* alphabetical order.
*/
{
const CFGMAP_ENTRY *const *ppE1 = (const CFGMAP_ENTRY * const *)v1,
*const *ppE2 = (const CFGMAP_ENTRY * const *)v2;
return strcasecmp((*ppE1)->symbol, (*ppE2)->symbol);
}
void
CFGMAP_print(CFGMAP * self, FILE * fh)
/******************************************************************
* Print to stream for debugging purposes
*/
{
size_t i, count = MAP_numItems(&self->entry_map);
CFGMAP_ENTRY *entryPtr_arr[count];
/* Load entry pointers into an array */
MAP_fetchAllItems(&self->entry_map, (void **)entryPtr_arr);
/* Sort the pointer array */
qsort(entryPtr_arr, count, sizeof(CFGMAP_ENTRY *), cmp_symbol);
/* Print the entries in sorted order */
for (i = 0; i < count; ++i) {
CFGMAP_ENTRY_print(entryPtr_arr[i], fh);
}
}
static int
unused_load_arr(void *item_ptr, void *data)
/******************************************************************
* lambda function to load unused CFGMAP_ENTRY's into an array.
*/
{
CFGMAP_ENTRY *e = (CFGMAP_ENTRY *) item_ptr;
/* If it was not ever looked up, it was unused */
if (!e->nLookups) {
void ***ppp = (void ***)data;
**ppp = item_ptr;
++(*ppp);
}
return 0;
}
size_t
CFGMAP_numUnused_symbols(CFGMAP * self)
/******************************************************************
* Print unused symbol count.
*/
{
size_t count = MAP_numItems(&self->entry_map);
CFGMAP_ENTRY *entryPtr_arr[count + 1], **ppEntry;
/* Zero out the array so we can find the end */
memset(entryPtr_arr, 0, (count + 1) * sizeof(CFGMAP_ENTRY *));
/* Load entry pointers into an array */
ppEntry = entryPtr_arr;
MAP_visitAllEntries(&self->entry_map, unused_load_arr, &ppEntry);
/* Find out how many entrys are unreferenced */
for (count = 0; entryPtr_arr[count]; ++count) ;
return count;
}
void
CFGMAP_print_unused_symbols(CFGMAP * self, FILE * fh)
/******************************************************************
* Print unused symbols for troubleshooting
*/
{
size_t count = MAP_numItems(&self->entry_map);
CFGMAP_ENTRY *entryPtr_arr[count + 1], **ppEntry;
/* Zero out the array so we can find the end */
memset(entryPtr_arr, 0, (count + 1) * sizeof(CFGMAP_ENTRY *));
/* Load entry pointers into an array */
ppEntry = entryPtr_arr;
MAP_visitAllEntries(&self->entry_map, unused_load_arr, &ppEntry);
/* Find out how many entrys are unreferenced */
for (count = 0; entryPtr_arr[count]; ++count) ;
/* Sort the pointer array */
qsort(entryPtr_arr, count, sizeof(CFGMAP_ENTRY *), cmp_symbol);
/* Print the entries in sorted order, stopping when null pointer is encountered */
for (ppEntry = entryPtr_arr; *ppEntry; ++ppEntry) {
CFGMAP_ENTRY_print(*ppEntry, fh);
}
}
unsigned int
CFGMAP_numEntries(CFGMAP * self)
/******************************************************************
* Return the number of entries stored in the configuration map.
*/
{
return MAP_numItems(&self->entry_map);
}
static int count_tuples(void *item_ptr, void *data)
/******************************************************************
* lambda function to count tuples
*/
{
const CFGMAP_ENTRY *e = (const CFGMAP_ENTRY *)item_ptr;
unsigned *pCount = (unsigned *)data;
*pCount += CFGMAP_ENTRY_numValues(e);
return 0;
}
unsigned int
CFGMAP_numTuples(CFGMAP * self)
/******************************************************************
* Return the number of tuples stored in the configuration map.
*/
{
/* Optimization to avoid recounting tuples */
if (!(self->flags & CFGMAP_NTUPLES_KNOWN_FLG)) {
MAP_visitAllEntries(&self->entry_map, count_tuples, &self->nTuples);
self->flags |= CFGMAP_NTUPLES_KNOWN_FLG;
}
return self->nTuples;
}
unsigned
CFGMAP_find_tuples(CFGMAP * self, struct CFGMAP_tuple rtn_arr[],
const char *symbol)
/************************************************************************
* Find all tuples with a key matching symbol
* returns the number of tuples which were populated.
*/
{
unsigned i, rtn = 0;
const CFGMAP_ENTRY *e;
/* See if there is a corresponding entry */
if (!(e = CFGMAP_find(self, symbol)))
return 0;
/* Now, load up the return buffer */
for (i = 0; i < CFGMAP_ENTRY_numValues(e); ++i) {
struct CFGMAP_tuple *t = rtn_arr + rtn;
t->key = e->symbol;
t->value = CFGMAP_ENTRY_value_sn(e, &t->serial_no, i);
++rtn;
}
return rtn;
}
/*===========================================================================================*/
/*========================== VALUE ==========================================================*/
/*===========================================================================================*/
static VALUE*
VALUE_constructor(VALUE * self, const char *str, unsigned serial_no)
/************************************************************************
* Prepare for use.
*/
{
self->str = strdup(str);
self->serial_no = serial_no;
return self;
}
static void*
VALUE_destructor(VALUE * self)
/************************************************************************
* Free resources.
*/
{
if (self->str)
free(self->str);
return self;
}
/*===========================================================================================*/
/*======================== CFGMAP_ENTRY ===================================================*/
/*===========================================================================================*/
void
CFGMAP_ENTRY_print(const CFGMAP_ENTRY * self, FILE * fh)
/************************************************************************
* Print information for debugging.
*/
{
unsigned count, i;
fprintf(fh, "\"%s\":", self->symbol);
for (i = 0, count = CFGMAP_ENTRY_numValues(self); i < count; ++i) {
unsigned sn;
const char *str = CFGMAP_ENTRY_value_sn(self, &sn, i);
fprintf(fh, "\n\t%u\t\"%s\"", sn, str);
}
fprintf(fh, "\n");
}
const char*
CFGMAP_ENTRY_symbol(const CFGMAP_ENTRY * self)
/******************************************************************
* Return the number of values stored for a given entry.
*/
{
return self->symbol;
}
unsigned int
CFGMAP_ENTRY_numValues(const CFGMAP_ENTRY * self)
/******************************************************************
* Return the number of values stored for a given entry.
*/
{
return PTRVEC_numItems(&self->value_lst);
}
static VALUE*
CFGMAP_ENTRY_append_value(CFGMAP_ENTRY * self,
const char *val_str,
unsigned serial_no)
/******************************************************************
* append a value to this entry
*/
{
VALUE *v;
VALUE_create(v, val_str, serial_no);
assert(v);
PTRVEC_addTail(&self->value_lst, v);
return v;
}
const char*
CFGMAP_ENTRY_value(const CFGMAP_ENTRY * self, unsigned ndx)
/******************************************************************
* Return the string for a the value indicated by ndx.
*/
{
const VALUE *v = PTRVEC_ndxPtr(&self->value_lst, ndx);
return v ? v->str : NULL;
}
const char*
CFGMAP_ENTRY_value_sn(const CFGMAP_ENTRY * self, unsigned *sn_buf, unsigned ndx)
/******************************************************************
* Return the string *and* serial number for a the value indicated by ndx.
*/
{
const VALUE *v = PTRVEC_ndxPtr(&self->value_lst, ndx);
/* Put serial number into supplied buffer */
if (v && sn_buf) {
*sn_buf = v->serial_no;
}
return v ? v->str : NULL;
}
static CFGMAP_ENTRY*
CFGMAP_ENTRY_constructor(CFGMAP_ENTRY * self, const char *symbol, unsigned symLen)
/************************************************************************
* Prepare a new CFGMAP_ENTRY for use.
*/
{
if (!self)
return NULL;
memset(self, 0, sizeof(*self));
self->symLen = symLen;
if (!PTRVEC_constructor(&self->value_lst, 10) ||
!(self->symbol = strdup(symbol)))
return NULL;
return self;
}
static void*
CFGMAP_ENTRY_destructor(CFGMAP_ENTRY * self)
/************************************************************************
* Free resources associated with a configuration entry.
*/
{
VALUE *v;
while ((v = PTRVEC_remHead(&self->value_lst))) {
VALUE_destroy(v);
}
PTRVEC_destructor(&self->value_lst);
if (self->symbol)
free(self->symbol);
return self;
}
static int
str_extract_string(char **rtnVal, const char **pStr)
/*****************************************************************
* Extract a substring from the string, advance pointer forward.
*/
{
const char *str = *pStr;
int rtn = 1, len;
/* Skip leading whitespace */
*pStr= skipspace(*(char**)pStr);
if (**pStr == '"') { /* Quoted string */
/* Skip the quote */
++(*pStr);
/* Find how far to matching quote */
for (len = 0; (*pStr)[len] && (*pStr)[len] != '"'; ++len) ;
/* Make sure there was a matching quote */
if ((*pStr)[len] == '\0')
goto abort;
/* Allocate buffer to store string */
*rtnVal = malloc(len + 1);
assert(*rtnVal);
/* Copy string into buffer */
memcpy(*rtnVal, *pStr, len);
/* Null terminate buffer */
(*rtnVal)[len] = '\0';
/* Advance pointer past second quote */
*pStr += len + 1;
} else { /* Unquoted string */
/* Find how far to end of string */
for (len = 0; ((*pStr)[len] && !isspace((*pStr)[len])); ++len) ;
/* Allocate buffer to store string */
*rtnVal = malloc(len + 1);
assert(*rtnVal);
/* Copy string into buffer */
memcpy(*rtnVal, *pStr, len);
/* Null terminate string */
(*rtnVal)[len] = '\0';
/* Advance pointer past end of string */
*pStr += len;
}
/* Successful return */
rtn = 0;
abort:
if (rtn)
eprintf("ERROR: \"%s\" is not a valid string specification.", str);
return rtn;
}
int
CFGMAP_obtain_prefix(char *rtnBuf, size_t buf_sz, const char *path)
/******************************************************************
* Given a full path string, place the prefix in rtnBuf.
*/
{
assert(path);
int rtn= EOF-1;
const char *pc,
*end= strrchr(path, '\\');
if(!end || path == end)
end= strchr(path, '\0');
if(('\\' != *path) || (path == end) || (path+1 == end)) {
eprintf("ERROR: cannot obtain prefix from \"%s\"", path);
goto abort;
}
unsigned nChars= 0;
for(pc= path; nChars < buf_sz && *pc && pc != end; ++pc) {
rtnBuf[nChars]= *pc;
++nChars;
}
if(nChars == buf_sz) {
rtn= EOF;
goto abort;
}
rtnBuf[nChars]= '\0';
rtn= 0;
abort:
return rtn;
}