/*
* Very simple configuration handling functions
* Programmed and designed by Matti 'ccr' Hamalainen
* (C) Copyright 2004 Tecnic Software productions (TNSP)
*
* Please read file 'COPYING' for information on license and distribution.
*/
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "th_config.h"
#include "th_util.h"
#include "th_string.h"
void th_config_error(char *pcFilename, size_t lineNum, const char *pcFormat, ...)
{
va_list ap;
va_start(ap, pcFormat);
fprintf(stderr, "%s: Error in '%s', line #%d: ", th_prog_name, pcFilename, lineNum);
vfprintf(stderr, pcFormat, ap);
va_end(ap);
}
t_config * th_config_new(void)
{
t_config *cfg;
cfg = (t_config *) calloc(1, sizeof(t_config));
if (!cfg) return NULL;
return cfg;
}
void th_config_free(t_config *cfg)
{
t_config_item *pCurr, *pNext;
if (!cfg) return;
pCurr = cfg->pItems;
while (pCurr)
{
pNext = pCurr->pNext;
free(pCurr->itemName);
free(pCurr);
pCurr = pNext;
}
}
/*
* Allocate and add new item to configuration
*/
t_config_item * th_config_add(t_config *cfg, char *itemName, int itemType, BOOL (*itemValidate)(t_config_item *), void *itemData)
{
t_config_item *pNode;
if (!cfg) return NULL;
/* Allocate new item */
pNode = (t_config_item *) calloc(1, sizeof(t_config_item));
if (!pNode) return NULL;
/* Set values */
pNode->itemType = itemType;
pNode->itemData = itemData;
pNode->itemValidate = itemValidate;
th_strcpy(&pNode->itemName, itemName);
/* Insert into linked list */
if (cfg->pItems)
{
/* The first node's pPrev points to last node */
LPREV = cfg->pItems->pPrev; /* New node's prev = Previous last node */
cfg->pItems->pPrev->pNext = pNode; /* Previous last node's next = New node */
cfg->pItems->pPrev = pNode; /* New last node = New node */
LNEXT = NULL; /* But next is NULL! */
} else {
cfg->pItems = pNode; /* First node ... */
LPREV = pNode; /* ... it's also last */
LNEXT = NULL; /* But next is NULL! */
}
return pNode;
}
/* Add integer type setting into give configuration
*/
int th_config_add_int(t_config *cfg, char *itemName, BOOL (*itemValidate)(t_config_item *), int *itemData, int itemDef)
{
t_config_item *pNode;
pNode = th_config_add(cfg, itemName, ITEM_INT, itemValidate, (void *) itemData);
if (!pNode) return -1;
*itemData = itemDef;
return 0;
}
/* Add unsigned integer type setting into give configuration
*/
int th_config_add_uint(t_config *cfg, char *itemName, BOOL (*itemValidate)(t_config_item *), t_uint *itemData, t_uint itemDef)
{
t_config_item *pNode;
pNode = th_config_add(cfg, itemName, ITEM_UINT, itemValidate, (void *) itemData);
if (!pNode) return -1;
*itemData = itemDef;
return 0;
}
/* Add strint type setting into given configuration
*/
int th_config_add_str(t_config *cfg, char *itemName, BOOL (*itemValidate)(t_config_item *), char **itemData, char *itemDef)
{
t_config_item *pNode;
pNode = th_config_add(cfg, itemName, ITEM_STRING, itemValidate, (void *) itemData);
if (!pNode) return -1;
if (itemDef != NULL)
*itemData = th_strdup(itemDef);
else
*itemData = NULL;
return 0;
}
/* Add boolean type setting into given configuration
*/
int th_config_add_bool(t_config *cfg, char *itemName, BOOL (*itemValidate)(t_config_item *), BOOL *itemData, BOOL itemDef)
{
t_config_item *pNode;
pNode = th_config_add(cfg, itemName, ITEM_BOOL, itemValidate, (void *) itemData);
if (!pNode) return -1;
*itemData = itemDef;
return 0;
}
/* Read a given file into configuration structure and variables
*/
enum {
PM_EOF,
PM_ERROR,
PM_NORMAL,
PM_COMMENT,
PM_NEXT,
PM_KEYNAME,
PM_KEYSET,
PM_STRING,
PM_INT,
PM_BOOL,
};
#define VADDCH(ch) if (strPos < SET_MAX_BUF) { tmpStr[strPos++] = ch; }
#define VISEND(ch) (ch == '\r' || ch == '\n' || ch == ';' || th_isspace(c))
int th_config_read(char *pcFilename, t_config *cfg)
{
FILE *inFile;
t_config_item *pItem;
char tmpStr[SET_MAX_BUF + 1];
size_t lineNum, strPos;
int c, parseMode, prevMode, nextMode, tmpCh;
BOOL isFound, isStart, tmpBool, isError;
assert(cfg);
/* Open the file */
if ((inFile = fopen(pcFilename, "ra")) == NULL)
return -1;
/* Parse the configuration */
lineNum = 1;
c = -1;
nextMode = prevMode = parseMode = PM_NORMAL;
while ((parseMode != PM_EOF) && (parseMode != PM_ERROR))
{
if (c == -1)
{
/* Get next character */
switch (c = fgetc(inFile)) {
case EOF:
if (parseMode != PM_NORMAL)
{
th_config_error(pcFilename, lineNum,
"Unexpected end of file.\n");
parseMode = PM_ERROR;
} else
parseMode = PM_EOF;
break;
case '\n':
lineNum++;
}
}
switch (parseMode) {
case PM_COMMENT:
/* Comment parsing mode */
if (c == '\n')
{
/* End of line, end of comment */
parseMode = prevMode;
prevMode = PM_COMMENT;
}
c = -1;
break;
case PM_NORMAL:
/* Normal parsing mode */
if (c == '#')
{
prevMode = parseMode;
parseMode = PM_COMMENT;
c = -1;
} else
if (VISEND(c))
{
c = -1;
} else
if (th_isalpha(c))
{
/* Start of key name found */
prevMode = parseMode;
parseMode = PM_KEYNAME;
strPos = 0;
} else
{
/* Error! Invalid character found */
th_config_error(pcFilename, lineNum,
"Unexpected character '%c'\n", c);
parseMode = PM_ERROR;
}
break;
case PM_KEYNAME:
/* Configuration KEY name parsing mode */
if (c == '#')
{
/* Start of comment */
prevMode = parseMode;
parseMode = PM_COMMENT;
c = -1;
} else
if (th_iscrlf(c) || th_isspace(c) || c == '=')
{
/* End of key name */
prevMode = parseMode;
parseMode = PM_NEXT;
nextMode = PM_KEYSET;
} else
if (th_isalnum(c) || (c == '_'))
{
/* Add to key name string */
VADDCH(c) else
{
/* Error! Key name string too long! */
th_config_error(pcFilename, lineNum,
"Config key name too long!");
parseMode = PM_ERROR;
}
c = -1;
} else
{
/* Error! Invalid character found */
th_config_error(pcFilename, lineNum,
"Unexpected character '%c'\n", c);
parseMode = PM_ERROR;
}
break;
case PM_KEYSET:
if (c == '=')
{
/* Find key from configuration */
tmpStr[strPos] = 0;
isFound = FALSE;
pItem = cfg->pItems;
while (pItem && !isFound)
{
if (strcmp(pItem->itemName, tmpStr) == 0)
isFound = TRUE;
else
pItem = pItem->pNext;
}
/* Check if key was found */
if (isFound)
{
/* Okay, set next mode */
switch (pItem->itemType) {
case ITEM_STRING: nextMode = PM_STRING; break;
case ITEM_INT:
case ITEM_UINT: nextMode = PM_INT; break;
case ITEM_BOOL: nextMode = PM_BOOL; break;
}
prevMode = parseMode;
parseMode = PM_NEXT;
isStart = TRUE;
strPos = 0;
} else
{
/* Error! No configuration key by this name found */
th_config_error(pcFilename, lineNum,
"No such configuration setting ('%s')\n", tmpStr);
parseMode = PM_ERROR;
}
c = -1;
} else
{
/* Error! '=' expected! */
th_config_error(pcFilename, lineNum,
"Unexpected character '%c', '=' expected.\n", c);
parseMode = PM_ERROR;
}
break;
case PM_NEXT:
/* Search next item parsing mode */
if (c == '#')
{
/* Start of comment */
prevMode = parseMode;
parseMode = PM_COMMENT;
} else
if (th_isspace(c) || th_iscrlf(c))
{
/* Ignore whitespaces and linechanges */
c = -1;
} else
{
/* Next item found */
prevMode = parseMode;
parseMode = nextMode;
}
break;
case PM_STRING:
/* String parsing mode */
if (isStart)
{
/* Start of string, get delimiter */
tmpCh = c;
isStart = FALSE;
strPos = 0;
} else
if (c == tmpCh)
{
/* End of string, set the value */
tmpStr[strPos] = 0;
th_strcpy((char **) pItem->itemData, tmpStr);
if (pItem->itemValidate)
pItem->itemValidate(pItem);
prevMode = parseMode;
parseMode = PM_NORMAL;
} else
{
/* Add character to string */
VADDCH(c) else
{
/* Error! String too long! */
th_config_error(pcFilename, lineNum,
"String too long! Maximum is %d characters.", SET_MAX_BUF);
parseMode = PM_ERROR;
}
}
c = -1;
break;
case PM_INT:
/* Integer parsing mode */
if (isStart && (pItem->itemType == ITEM_UINT) && (c == '-'))
{
/* Error! Negative values not allowed for unsigned ints */
th_config_error(pcFilename, lineNum,
"Negative value specified, unsigned value expected.");
parseMode = PM_ERROR;
} else
if (isStart && (c == '-' || c == '+'))
{
VADDCH(c) else isError = TRUE;
} else
if (th_isdigit(c))
{
VADDCH(c) else isError = TRUE;
} else
if (VISEND(c))
{
/* End of integer parsing mode */
tmpStr[strPos] = 0;
switch (pItem->itemType) {
case ITEM_INT:
*((int *) pItem->itemData) = atoi(tmpStr);
break;
case ITEM_UINT:
*((t_uint *) pItem->itemData) = atol(tmpStr);
break;
}
if (pItem->itemValidate)
pItem->itemValidate(pItem);
prevMode = parseMode;
parseMode = PM_NORMAL;
} else
{
/* Error! Unexpected character. */
th_config_error(pcFilename, lineNum,
"Unexpected character, ", SET_MAX_BUF);
parseMode = PM_ERROR;
}
if (isError)
{
/* Error! String too long! */
th_config_error(pcFilename, lineNum,
"String too long! Maximum is %d characters.", SET_MAX_BUF);
parseMode = PM_ERROR;
}
isStart = FALSE;
c = -1;
break;
case PM_BOOL:
/* Boolean parsing mode */
if (isStart)
{
tmpCh = c;
isStart = FALSE;
} else
if (VISEND(c))
{
/* End of boolean parsing */
switch (tmpCh) {
case 'Y': case 'y':
case 'T': case 't':
case '1':
tmpBool = TRUE;
break;
default:
tmpBool = FALSE;
break;
}
/* Set the value */
*((BOOL *) pItem->itemData) = tmpBool;
if (pItem->itemValidate)
pItem->itemValidate(pItem);
prevMode = parseMode;
parseMode = PM_NORMAL;
}
c = -1;
break;
}
}
/* Close files */
fclose(inFile);
/* Return result */
if (parseMode == PM_ERROR)
return -2;
else
return 0;
}
/* Validate a given configuration. If setting does not have a defined
* validation function (function pointer is NULL), value is assumed to be valid.
*
* Returns 0 if ok, otherwise positive integer with number of failed validations
* is returned.
*/
int th_config_validate(t_config *cfg)
{
t_config_item *pNode;
int nFailed;
assert(cfg);
nFailed = 0;
pNode = cfg->pItems;
while (pNode)
{
if (pNode->itemValidate && !pNode->itemValidate(pNode))
nFailed++;
pNode = pNode->pNext;
}
return nFailed;
}
syntax highlighted by Code2HTML, v. 0.9.1