changeset 13:adcbcac66125

Import improved config code from chat client fork of th-libs.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 29 Oct 2010 13:48:31 +0300
parents 83f7c71e4772
children e5e6370217ef
files th_config.c
diffstat 1 files changed, 546 insertions(+), 479 deletions(-) [+]
line wrap: on
line diff
--- a/th_config.c	Fri Oct 29 13:47:57 2010 +0300
+++ b/th_config.c	Fri Oct 29 13:48:31 2010 +0300
@@ -14,563 +14,630 @@
 #include "th_util.h"
 #include "th_string.h"
 
-#define SET_MAX_BUF	(8192)
-#define LPREV		(pNode->prev)
-#define LNEXT		(pNode->next)
-
-
-void th_config_error(char * filename, size_t lineNum,
-	const char * pcFormat, ...)
-{
-	va_list ap;
-	va_start(ap, pcFormat);
-	fprintf(stderr, "%s: '%s', line #%d: ", th_prog_name, filename, lineNum);
-	vfprintf(stderr, pcFormat, ap);
-	va_end(ap);
-}
-
-
-/* Create a new configuration
- */
-th_config_t *th_config_new(void)
-{
-	th_config_t *cfg;
-
-	cfg = (th_config_t *) th_calloc(1, sizeof(th_config_t));
-	if (!cfg)
-		return NULL;
-
-	return cfg;
-}
+#define SET_MAX_BUF     (8192)
 
 
 /* Free a given configuration (the values are not free'd)
  */
-void th_config_free(th_config_t * cfg)
+void th_config_free(cfgitem_t *cfg)
 {
-	th_cfgitem_t *curr, *next;
-
-	if (!cfg)
-		return;
+    cfgitem_t *curr = cfg;
 
-	curr = cfg->items;
-	while (curr) {
-		next = curr->next;
-		th_free(curr->itemName);
-		th_free(curr);
-		curr = next;
-	}
+    while (curr != NULL) {
+        cfgitem_t *next = curr->next;
+        
+        if (curr->type == ITEM_BLOCK)
+            th_config_free((cfgitem_t *) curr->data);
+        
+        th_free(curr->name);
+        th_free(curr);
+        curr = next;
+    }
 }
 
 
 /* Allocate and add new item to configuration
  */
-th_cfgitem_t *th_config_add(th_config_t * cfg, char * itemName, int itemType,
-			     BOOL(*itemValidate) (th_cfgitem_t *), void *itemData)
+static cfgitem_t *th_config_add(cfgitem_t **cfg, char *name, int type,
+                 BOOL (*validate)(cfgitem_t *), void *data)
 {
-	th_cfgitem_t *pNode;
+    cfgitem_t *node;
 
-	if (!cfg)
-		return NULL;
+    if (cfg == NULL)
+        return NULL;
 
-	/* Allocate new item */
-	pNode = (th_cfgitem_t *) th_calloc(1, sizeof(th_cfgitem_t));
-	if (!pNode)
-		return NULL;
+    /* Allocate new item */
+    node = (cfgitem_t *) th_calloc(1, sizeof(cfgitem_t));
+    if (node == NULL)
+        return NULL;
 
-	/* Set values */
-	pNode->itemType = itemType;
-	pNode->itemData = itemData;
-	pNode->itemValidate = itemValidate;
-	th_pstrcpy(&pNode->itemName, itemName);
+    /* Set values */
+    node->type = type;
+    node->data = data;
+    node->validate = validate;
+    node->name = th_strdup(name);
+    
+    /* Insert into linked list */
+    if (*cfg != NULL) {
+        node->prev = (*cfg)->prev;
+        (*cfg)->prev->next = node;
+        (*cfg)->prev = node;
+    } else {
+        *cfg = node;
+        node->prev = node;
+    }
+    node->next = NULL;
 
-	/* Insert into linked list */
-	if (cfg->items) {
-		/* The first node's prev points to last node */
-		LPREV = cfg->items->prev;	/* New node's prev = Previous last node */
-		cfg->items->prev->next = pNode;	/* Previous last node's next = New node */
-		cfg->items->prev = pNode;	/* New last node = New node */
-		LNEXT = NULL;	/* But next is NULL! */
-	} else {
-		cfg->items = pNode;	/* First node ... */
-		LPREV = pNode;	/* ... it's also last */
-		LNEXT = NULL;	/* But next is NULL! */
-	}
-
-	return pNode;
+    return node;
 }
 
 
 /* Add integer type setting into give configuration
  */
-int th_config_add_int(th_config_t * cfg, char * itemName, BOOL(*itemValidate) (th_cfgitem_t *),
-		      int *itemData, int itemDef)
+int th_config_add_int(cfgitem_t ** cfg, char * name, BOOL(*itemValidate) (cfgitem_t *),
+              int *itemData, int itemDef)
 {
-	th_cfgitem_t *pNode;
+    cfgitem_t *node;
 
-	pNode = th_config_add(cfg, itemName, ITEM_INT, itemValidate, (void *) itemData);
-	if (!pNode)
-		return -1;
+    node = th_config_add(cfg, name, ITEM_INT, itemValidate, (void *) itemData);
+    if (node == NULL)
+        return -1;
 
-	*itemData = itemDef;
+    *itemData = itemDef;
 
-	return 0;
+    return 0;
 }
 
 
 /* Add unsigned integer type setting into give configuration
  */
-int th_config_add_uint(th_config_t * cfg, char * itemName, BOOL(*itemValidate) (th_cfgitem_t *),
-		       unsigned int * itemData, unsigned int itemDef)
+int th_config_add_uint(cfgitem_t ** cfg, char * name, BOOL(*itemValidate) (cfgitem_t *),
+               unsigned int * itemData, unsigned int itemDef)
 {
-	th_cfgitem_t *pNode;
+    cfgitem_t *node;
 
-	pNode = th_config_add(cfg, itemName, ITEM_UINT, itemValidate, (void *) itemData);
-	if (!pNode)
-		return -1;
+    node = th_config_add(cfg, name, ITEM_UINT, itemValidate, (void *) itemData);
+    if (node == NULL)
+        return -1;
 
-	*itemData = itemDef;
+    *itemData = itemDef;
 
-	return 0;
+    return 0;
 }
 
 
 /* Add strint type setting into given configuration
  */
-int th_config_add_str(th_config_t * cfg, char * itemName, BOOL(*itemValidate) (th_cfgitem_t *),
-		      char ** itemData, char * itemDef)
+int th_config_add_str(cfgitem_t ** cfg, char * name, BOOL(*itemValidate) (cfgitem_t *),
+              char ** itemData, char * itemDef)
 {
-	th_cfgitem_t *pNode;
+    cfgitem_t *node;
 
-	pNode = th_config_add(cfg, itemName, ITEM_STRING, itemValidate, (void *) itemData);
-	if (!pNode)
-		return -1;
+    node = th_config_add(cfg, name, ITEM_STRING, itemValidate, (void *) itemData);
+    if (node == NULL)
+        return -1;
 
-	if (itemDef != NULL)
-		*itemData = strdup(itemDef);
-	else
-		*itemData = NULL;
+    *itemData = th_strdup(itemDef);
 
-	return 0;
+    return 0;
 }
 
 
 /* Add boolean type setting into given configuration
  */
-int th_config_add_bool(th_config_t * cfg, char * itemName, BOOL(*itemValidate) (th_cfgitem_t *),
-		       BOOL * itemData, BOOL itemDef)
+int th_config_add_bool(cfgitem_t ** cfg, char * name, BOOL(*itemValidate) (cfgitem_t *),
+               BOOL * itemData, BOOL itemDef)
 {
-	th_cfgitem_t *pNode;
+    cfgitem_t *node;
+
+    node = th_config_add(cfg, name, ITEM_BOOL, itemValidate, (void *) itemData);
+    if (node == NULL)
+        return -1;
+
+    *itemData = itemDef;
+
+    return 0;
+}
+
 
-	pNode = th_config_add(cfg, itemName, ITEM_BOOL, itemValidate, (void *) itemData);
-	if (!pNode)
-		return -1;
+/* Add implicit comment
+ */
+int th_config_add_comment(cfgitem_t ** cfg, char * comment)
+{
+    cfgitem_t *node;
+
+    node = th_config_add(cfg, comment, ITEM_COMMENT, NULL, NULL);
+    if (node == NULL)
+        return -1;
+
+    return 0;
+}
 
-	*itemData = itemDef;
 
-	return 0;
+/* Add new block
+ */
+int th_config_add_section(cfgitem_t ** cfg, char * name, cfgitem_t *data)
+{
+    cfgitem_t *node;
+    
+    node = th_config_add(cfg, name, ITEM_BLOCK, NULL, (void *) data);
+    if (node == NULL)
+        return -1;
+
+    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
+enum {
+    PM_EOF,
+    PM_ERROR,
+    PM_NORMAL,
+    PM_COMMENT,
+    PM_NEXT,
+    PM_KEYNAME,
+    PM_KEYSET,
+    PM_STRING,
+    PM_INT,
+    PM_BOOL,
+    PM_BLOCK
 };
 
 #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 * filename, th_config_t * cfg)
-{
-	FILE *inFile;
-	th_cfgitem_t *item;
-	char tmpStr[SET_MAX_BUF + 1];
-	size_t lineNum, strPos;
-	int c, parseMode, prevMode, nextMode, tmpCh;
-	BOOL isFound, isStart, tmpBool, isError, validError;
-
-	assert(cfg);
-
-	/* Open the file */
-	if ((inFile = fopen(filename, "rb")) == NULL)
-		return -1;
-
-	/* Initialize values */
-	item = NULL;
-	tmpCh = 0;
-	strPos = 0;
-	lineNum = 1;
-	c = -1;
-	isFound = isStart = tmpBool = isError = validError = FALSE;
-	nextMode = prevMode = parseMode = PM_NORMAL;
-
-	/* Parse the configuration */
-	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(filename, 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;
+#define VISEND(ch) (ch == '\r' || ch == '\n' || ch == ';' || th_isspace(c) || ch == '#')
 
-		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(filename, lineNum,
-					"Unexpected character '%c'\n", c);
-				parseMode = PM_ERROR;
-			}
-			break;
+typedef struct {
+    FILE *file;
+    char *filename;
+    size_t line;
+} conffile_t;
 
-		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(filename, lineNum,
-						"Config key name too long!");
-					parseMode = PM_ERROR;
-				}
-				c = -1;
-			} else {
-				/* Error! Invalid character found */
-				th_config_error(filename, lineNum,
-					"Unexpected character '%c'\n", c);
-				parseMode = PM_ERROR;
-			}
-			break;
-
-		case PM_KEYSET:
-			if (c == '=') {
-				/* Find key from configuration */
-				tmpStr[strPos] = 0;
-				isFound = FALSE;
-				item = cfg->items;
-				while (item && !isFound) {
-					if (strcmp(item->itemName, tmpStr) == 0)
-						isFound = TRUE;
-					else
-						item = item->next;
-				}
-
-				/* Check if key was found */
-				if (isFound) {
-					/* Okay, set next mode */
-					switch (item->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(filename, lineNum,
-						"No such configuration setting ('%s')\n",
-						tmpStr);
-					parseMode = PM_ERROR;
-				}
-
-				c = -1;
-			} else {
-				/* Error! '=' expected! */
-				th_config_error(filename, 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_pstrcpy((char **) item->itemData, tmpStr);
-				if (item->itemValidate) {
-					if (!item->itemValidate(item))
-						validError = TRUE;
-				}
-
-				prevMode = parseMode;
-				parseMode = PM_NORMAL;
-			} else {
-				/* Add character to string */
-				VADDCH(c)
-				else
-				{
-					/* Error! String too long! */
-					th_config_error(filename, 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 && (item->itemType == ITEM_UINT) && (c == '-')) {
-				/* Error! Negative values not allowed for unsigned ints */
-				th_config_error(filename, 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 (item->itemType) {
-				case ITEM_INT:
-					*((int *) item->itemData) = atoi(tmpStr);
-					break;
-
-				case ITEM_UINT:
-					*((unsigned int *) item->itemData) = atol(tmpStr);
-					break;
-				}
-				if (item->itemValidate) {
-					if (!item->itemValidate(item))
-						validError = TRUE;
-				}
-
-				prevMode = parseMode;
-				parseMode = PM_NORMAL;
-			} else {
-				/* Error! Unexpected character. */
-				th_config_error(filename, lineNum,
-						"Unexpected character, ", SET_MAX_BUF);
-				parseMode = PM_ERROR;
-			}
-
-			if (isError) {
-				/* Error! String too long! */
-				th_config_error(filename, 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 *) item->itemData) = tmpBool;
-				if (item->itemValidate) {
-					if (!item->itemValidate(item))
-						validError = TRUE;
-				}
-
-				prevMode = parseMode;
-				parseMode = PM_NORMAL;
-			}
-
-			c = -1;
-			break;
-		}
-	}
+static void th_config_error(conffile_t *f, const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    fprintf(stderr, "%s: '%s', line #%d: ", th_prog_name, f->filename, f->line);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+}
 
 
-	/* Close files */
-	fclose(inFile);
+static int th_config_read_sect(conffile_t *f, cfgitem_t * cfg, int nesting)
+{
+    cfgitem_t *item = NULL;
+    char tmpStr[SET_MAX_BUF + 1];
+    size_t strPos;
+    int c, parseMode, prevMode, nextMode, tmpCh;
+    BOOL isFound, isStart, isError, validError;
+
+    /* Initialize values */
+    tmpCh = 0;
+    strPos = 0;
+    c = -1;
+    isFound = isStart = isError = validError = FALSE;
+    nextMode = prevMode = parseMode = PM_NORMAL;
+
+    /* Parse the configuration */
+    while (parseMode != PM_EOF && parseMode != PM_ERROR) {
+        if (c == -1) {
+            /* Get next character */
+            switch (c = fgetc(f->file)) {
+            case EOF:
+                if (parseMode != PM_NORMAL) {
+                    th_config_error(f,
+                    "Unexpected end of file.\n");
+                    parseMode = PM_ERROR;
+                } else
+                    parseMode = PM_EOF;
+                break;
+
+            case '\n':
+                f->line++;
+            }
+        }
+
+        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 (c == '}') {
+                if (nesting > 0) {
+                    /* Check for validation errors */
+                    return (validError) ? 1 : 0;
+                } else {
+                    th_config_error(f,
+                    "HMMM!\n");
+                    parseMode = PM_ERROR;
+                }
+            } 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(f,
+                    "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(f,
+                        "Config key name too long!");
+                    parseMode = PM_ERROR;
+                }
+                c = -1;
+            } else {
+                /* Error! Invalid character found */
+                tmpStr[strPos] = 0;
+                th_config_error(f,
+                    "Unexpected character '%c' in key name '%s'.\n", c, tmpStr);
+                parseMode = PM_ERROR;
+            }
+            break;
+
+        case PM_KEYSET:
+            if (c == '=') {
+                /* Find key from configuration */
+                tmpStr[strPos] = 0;
+                isFound = FALSE;
+                item = cfg;
+                while (item != NULL && !isFound) {
+                    if (item->name != NULL && strcmp(item->name, tmpStr) == 0)
+                        isFound = TRUE;
+                    else
+                        item = item->next;
+                }
+
+                /* Check if key was found */
+                if (isFound) {
+                    /* Okay, set next mode */
+                    switch (item->type) {
+                    case ITEM_STRING:
+                        nextMode = PM_STRING;
+                        break;
+
+                    case ITEM_INT:
+                    case ITEM_UINT:
+                        nextMode = PM_INT;
+                        break;
+
+                    case ITEM_BOOL:
+                        nextMode = PM_BOOL;
+                        break;
+                    
+                    case ITEM_BLOCK:
+                        nextMode = PM_BLOCK;
+                        break;
+                    }
+
+                    prevMode = parseMode;
+                    parseMode = PM_NEXT;
+                    isStart = TRUE;
+                    strPos = 0;
+                } else {
+                    /* Error! No configuration key by this name found */
+                    th_config_error(f,
+                        "No such configuration setting ('%s')\n", tmpStr);
+                    parseMode = PM_ERROR;
+                }
+
+                c = -1;
+            } else {
+                /* Error! '=' expected! */
+                th_config_error(f,
+                    "Unexpected character '%c', assignation '=' was expected.\n", c);
+                parseMode = PM_ERROR;
+            }
+            break;
 
-	/* Check for validation errors */
-	if (validError)
-		return 1;
+        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_BLOCK:
+            /* Block parsing mode */
+            if (c != '{') {
+                /* Error! Block start '{' expected! */
+                th_config_error(f,
+                    "Unexpected character '%c', block start '{' was expected.\n", c);
+                parseMode = PM_ERROR;
+            } else {
+                int res = th_config_read_sect(f, (cfgitem_t *) item->data, nesting + 1);
+                c = -1;
+                if (res > 0)
+                    validError = TRUE;
+                else if (res < 0)
+                    parseMode = PM_ERROR;
+                else {
+                    prevMode = parseMode;
+                    parseMode = PM_NORMAL;
+                }
+            }
+            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_pstrcpy((char **) item->data, tmpStr);
+                if (item->validate != NULL) {
+                    if (!item->validate(item))
+                        validError = TRUE;
+                }
+
+                prevMode = parseMode;
+                parseMode = PM_NORMAL;
+            } else {
+                /* Add character to string */
+                VADDCH(c)
+                else
+                {
+                    /* Error! String too long! */
+                    th_config_error(f,
+                        "String too long! Maximum is %d characters.",
+                        SET_MAX_BUF);
+                    parseMode = PM_ERROR;
+                }
+            }
+
+            c = -1;
+            break;
+
+        case PM_INT:
+            /* Integer parsing mode */
+            if (isStart && item->type == ITEM_UINT && c == '-') {
+                /* Error! Negative values not allowed for unsigned ints */
+                th_config_error(f,
+                        "Negative value specified for %s, unsigned value expected.",
+                        item->name);
+                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 (item->type) {
+                case ITEM_INT:
+                    *((int *) item->data) = atoi(tmpStr);
+                    break;
 
-	/* Return result */
-	if (parseMode == PM_ERROR)
-		return -2;
-	else
-		return 0;
+                case ITEM_UINT:
+                    *((unsigned int *) item->data) = atol(tmpStr);
+                    break;
+                }
+                if (item->validate != NULL) {
+                    if (!item->validate(item))
+                        validError = TRUE;
+                }
+
+                prevMode = parseMode;
+                parseMode = PM_NORMAL;
+            } else {
+                /* Error! Unexpected character. */
+                th_config_error(f,
+                    "Unexpected character '%c' for integer setting '%s'.",
+                    c, item->name);
+                parseMode = PM_ERROR;
+            }
+
+            if (isError) {
+                /* Error! String too long! */
+                th_config_error(f, "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)) {
+                BOOL tmpBool;
+                
+                /* End of boolean parsing */
+                switch (tmpCh) {
+                case 'Y': case 'y':
+                case 'T': case 't':
+                case '1':
+                    tmpBool = TRUE;
+                    break;
+                
+                case 'N': case 'n':
+                case 'F': case 'f':
+                case '0':
+                    tmpBool = FALSE;
+                    break;
+                
+                default:
+                    isError = TRUE;
+                }
+                
+                if (isError) {
+                    th_config_error(f, "Invalid boolean value for '%s'.\n", item->name);
+                    parseMode = PM_ERROR;
+                } else {
+                    *((BOOL *) item->data) = tmpBool;
+                    if (item->validate != NULL) {
+                        if (!item->validate(item))
+                            validError = TRUE;
+                    }
+
+                    prevMode = parseMode;
+                    parseMode = PM_NORMAL;
+                }
+            }
+            c = -1;
+            break;
+        }
+    }
+
+    /* Check for validation errors */
+    if (validError)
+        return 1;
+
+    /* Return result */
+    if (parseMode == PM_ERROR)
+        return -2;
+    else
+        return 0;
+}
+
+
+int th_config_read(FILE *inFile, char *filename, cfgitem_t * cfg)
+{
+    conffile_t f;
+    
+    f.file = inFile;
+    f.filename = filename;
+    f.line = 1;
+    
+    return th_config_read_sect(&f, cfg, 0);
 }
 
 
 /* Write a configuration into file
  */
-int th_config_write(FILE * outFile, th_config_t * cfg)
+static void th_print_indent(conffile_t *f, int nesting)
 {
-	th_cfgitem_t *item;
+    int i;
+    for (i = 0; i < nesting * 2; i++)
+        fputc(' ', f->file);
+}
 
-	if (!cfg)
-		return -1;
-
-	fprintf(outFile, "# Configuration written by %s %s\n\n", 
-		th_prog_fullname, th_prog_version);
 
-	item = cfg->items;
-	while (item) {
-		if (!item->itemData || ((item->itemType == ITEM_STRING) &&
-			*(char **) item->itemData == NULL)) {
-			
-			fprintf(outFile, "#%s = ", item->itemName);
-			
-			switch (item->itemType) {
-			case ITEM_STRING:
-				fprintf(outFile, "\"string\"");
-				break;
+static int th_config_write_sect(conffile_t *f, cfgitem_t *item, int nesting)
+{
+    while (item != NULL) {
+        if (item->type == ITEM_COMMENT) {
+            th_print_indent(f, nesting);
+            if (fprintf(f->file, "# %s\n", (item->name != NULL) ? item->name : "" ) < 0)
+                return -1;
+        } else
+        if (item->name != NULL) {
+            th_print_indent(f, nesting);
+            
+            switch (item->type) {
+            case ITEM_STRING:
+                if (fprintf(f->file, "%s = \"%s\"\n",
+                    item->name, *((char **) item->data)) < 0)
+                    return -3;
+                break;
 
-			case ITEM_INT:
-				fprintf(outFile, "int");
-				break;
+            case ITEM_INT:
+                if (fprintf(f->file, "%s = %i\n",
+                    item->name, *((int *) item->data)) < 0)
+                    return -4;
+                break;
 
-			case ITEM_UINT:
-				fprintf(outFile, "uint");
-				break;
+            case ITEM_UINT:
+                if (fprintf(f->file, "%s = %d\n",
+                    item->name, *((unsigned int *) item->data)) < 0)
+                    return -5;
+                break;
 
-			case ITEM_BOOL:
-				fprintf(outFile, "boolean");
-				break;
-			}
-			
-		} else {
-			fprintf(outFile, "%s = ", item->itemName);
-			
-			switch (item->itemType) {
-			case ITEM_STRING:
-				fprintf(outFile, "\"%s\"",
-					*((char **) item->itemData));
-				break;
-
-			case ITEM_INT:
-				fprintf(outFile, "%i",
-					*((int *) item->itemData));
-				break;
+            case ITEM_BOOL:
+                if (fprintf(f->file, "%s = %s\n",
+                    item->name, *((BOOL *) item->data) ? "yes" : "no") < 0)
+                    return -6;
+                break;
+            
+            case ITEM_BLOCK:
+                {
+                int res;
+                if (fprintf(f->file, "\n%s = {\n", item->name) < 0)
+                    return -7;
+                res = th_config_write_sect(f, (cfgitem_t *) item->data, nesting + 1);
+                if (res != 0) return res;
+                if (fprintf(f->file, "} # End of '%s'\n\n", item->name) < 0)
+                    return -8;
+                }
+            }
+        }
+        item = item->next;
+    }
+    
+    return 0;
+}
 
-			case ITEM_UINT:
-				fprintf(outFile, "%d",
-					*((unsigned int *) item->itemData));
-				break;
+
+int th_config_write(FILE *outFile, char *filename, cfgitem_t *cfg)
+{
+    conffile_t f;
+    
+    if (cfg == NULL)
+        return -1;
 
-			case ITEM_BOOL:
-				fprintf(outFile, "%s",
-					*((BOOL *) item->itemData) ? "yes" : "no");
-				break;
-			}
-		}
-
-		fprintf(outFile, "\n\n");
-		item = item->next;
-	}
-
-	return 0;
+    f.file = outFile;
+    f.filename = filename;
+    f.line = 1;
+    
+    fprintf(outFile, "# Configuration written by %s %s\n\n", 
+        th_prog_fullname, th_prog_version);
+    
+    return th_config_write_sect(&f, cfg, 0);
 }