Mercurial > hg > nnchat
view th_config.c @ 138:3e221c16b087
Improvements in configuration file handing.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Tue, 02 Nov 2010 21:46:16 +0200 |
parents | 26fe4dab7e78 |
children | c6c3825376c9 |
line wrap: on
line source
/* * Very simple configuration handling functions * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2004-2010 Tecnic Software productions (TNSP) * * Please read file 'COPYING' for information on license and distribution. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include <stdio.h> #include <stdarg.h> #include "th_config.h" #include "th_util.h" #include "th_string.h" #define SET_MAX_BUF (8192) /* Free a given configuration (the values are not free'd) */ void th_cfg_free(cfgitem_t *cfg) { cfgitem_t *curr = cfg; while (curr != NULL) { cfgitem_t *next = curr->next; if (curr->type == ITEM_SECTION) th_cfg_free((cfgitem_t *) curr->data); th_free(curr->name); th_free(curr); curr = next; } } /* Allocate and add new item to configuration */ static cfgitem_t *th_cfg_add(cfgitem_t **cfg, const char *name, const int type, void *data) { cfgitem_t *node; if (cfg == NULL) return NULL; /* Allocate new item */ node = (cfgitem_t *) th_calloc(1, sizeof(cfgitem_t)); if (node == NULL) return NULL; /* Set values */ node->type = type; node->data = data; 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; return node; } /* Add integer type setting into give configuration */ int th_cfg_add_int(cfgitem_t ** cfg, char * name, int *itemData, int itemDef) { cfgitem_t *node; node = th_cfg_add(cfg, name, ITEM_INT, (void *) itemData); if (node == NULL) return -1; *itemData = itemDef; return 0; } int th_cfg_add_hexvalue(cfgitem_t ** cfg, char * name, int *itemData, int itemDef) { cfgitem_t *node; node = th_cfg_add(cfg, name, ITEM_HEX_TRIPLET, (void *) itemData); if (node == NULL) return -1; *itemData = itemDef; return 0; } /* Add unsigned integer type setting into give configuration */ int th_cfg_add_uint(cfgitem_t ** cfg, char * name, unsigned int * itemData, unsigned int itemDef) { cfgitem_t *node; node = th_cfg_add(cfg, name, ITEM_UINT, (void *) itemData); if (node == NULL) return -1; *itemData = itemDef; return 0; } /* Add strint type setting into given configuration */ int th_cfg_add_string(cfgitem_t ** cfg, char * name, char ** itemData, char * itemDef) { cfgitem_t *node; node = th_cfg_add(cfg, name, ITEM_STRING, (void *) itemData); if (node == NULL) return -1; *itemData = th_strdup(itemDef); return 0; } /* Add boolean type setting into given configuration */ int th_cfg_add_bool(cfgitem_t ** cfg, char * name, BOOL * itemData, BOOL itemDef) { cfgitem_t *node; node = th_cfg_add(cfg, name, ITEM_BOOL, (void *) itemData); if (node == NULL) return -1; *itemData = itemDef; return 0; } /* Add implicit comment */ int th_cfg_add_comment(cfgitem_t ** cfg, char * comment) { cfgitem_t *node; node = th_cfg_add(cfg, comment, ITEM_COMMENT, NULL); if (node == NULL) return -1; return 0; } /* Add new section */ int th_cfg_add_section(cfgitem_t ** cfg, char * name, cfgitem_t *data) { cfgitem_t *node; node = th_cfg_add(cfg, name, ITEM_SECTION, (void *) data); if (node == NULL) return -1; return 0; } int th_cfg_add_string_list(cfgitem_t ** cfg, char * name, qlist_t **data) { cfgitem_t *node; if (data == NULL) return -5; node = th_cfg_add(cfg, name, ITEM_STRING_LIST, (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, PM_SECTION, PM_ARRAY }; #define VADDCH(ch) if (strPos < SET_MAX_BUF) { tmpStr[strPos++] = ch; } #define VISEND(ch) (ch == '\r' || ch == '\n' || ch == ';' || th_isspace(c) || ch == '#') typedef struct { FILE *file; char *filename; size_t line; } conffile_t; static void th_cfg_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); } static int th_cfg_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_cfg_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_cfg_error(f, "Invalid nesting sequence encountered.\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_cfg_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_cfg_error(f, "Config key name too long!"); parseMode = PM_ERROR; } c = -1; } else { /* Error! Invalid character found */ tmpStr[strPos] = 0; th_cfg_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_HEX_TRIPLET: case ITEM_STRING: nextMode = PM_STRING; break; case ITEM_STRING_LIST: nextMode = PM_ARRAY; break; case ITEM_INT: case ITEM_UINT: nextMode = PM_INT; break; case ITEM_BOOL: nextMode = PM_BOOL; break; case ITEM_SECTION: nextMode = PM_SECTION; break; } prevMode = parseMode; parseMode = PM_NEXT; isStart = TRUE; strPos = 0; } else { /* Error! No configuration key by this name found */ th_cfg_error(f, "No such configuration setting ('%s')\n", tmpStr); parseMode = PM_ERROR; } c = -1; } else { /* Error! '=' expected! */ th_cfg_error(f, "Unexpected character '%c', assignation '=' was 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_ARRAY: if (isStart) { switch (item->type) { case ITEM_STRING_LIST: prevMode = parseMode; parseMode = PM_STRING; break; } } else if (c == ',') { switch (item->type) { case ITEM_STRING_LIST: prevMode = parseMode; parseMode = PM_STRING; break; } } else { prevMode = parseMode; parseMode = PM_NORMAL; } break; case PM_SECTION: /* Section parsing mode */ if (c != '{') { /* Error! Section start '{' expected! */ th_cfg_error(f, "Unexpected character '%c', section start '{' was expected.\n", c); parseMode = PM_ERROR; } else { int res = th_cfg_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; switch (item->type) { case ITEM_HEX_TRIPLET: *(int *) item->data = th_get_hex_triplet(tmpStr); prevMode = parseMode; parseMode = PM_NORMAL; break; case ITEM_STRING: th_pstrcpy((char **) item->data, tmpStr); prevMode = parseMode; parseMode = PM_NORMAL; break; case ITEM_STRING_LIST: // th_cfg_add_to_array(item, th_strdup(tmpStr)); prevMode = parseMode; parseMode = PM_NEXT; nextMode = PM_ARRAY; break; } } else { /* Add character to string */ VADDCH(c) else { /* Error! String too long! */ th_cfg_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_cfg_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; case ITEM_UINT: *((unsigned int *) item->data) = atol(tmpStr); break; } prevMode = parseMode; parseMode = PM_NORMAL; } else { /* Error! Unexpected character. */ th_cfg_error(f, "Unexpected character '%c' for integer setting '%s'.", c, item->name); parseMode = PM_ERROR; } if (isError) { /* Error! String too long! */ th_cfg_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_cfg_error(f, "Invalid boolean value for '%s'.\n", item->name); parseMode = PM_ERROR; } else { *((BOOL *) item->data) = tmpBool; 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_cfg_read(FILE *inFile, char *filename, cfgitem_t * cfg) { conffile_t f; f.file = inFile; f.filename = filename; f.line = 1; return th_cfg_read_sect(&f, cfg, 0); } /* Write a configuration into file */ static void th_print_indent(conffile_t *f, int nesting) { int i; for (i = 0; i < nesting * 2; i++) fputc(' ', f->file); } static int th_cfg_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 (*(item->val_str) == NULL) { if (fprintf(f->file, "#%s = \"\"\n", item->name) < 0) return -3; } else { if (fprintf(f->file, "%s = \"%s\"\n", item->name, *(item->val_str)) < 0) return -3; } break; case ITEM_STRING_LIST: if (*(item->list) == NULL) { if (fprintf(f->file, "#%s = \"\", \"\"\n", item->name) < 0) return -3; } else { qlist_t *node = *(item->list); size_t n = th_llist_length(node); if (fprintf(f->file, "%s = ", item->name) < 0) return -3; while (node != NULL) { if (node->data != NULL) fprintf(f->file, "\"%s\"", (char *) node->data); if (--n > 0) fprintf(f->file, ", "); node = node->next; } if (fprintf(f->file, "\n") < 0) return -3; } break; case ITEM_INT: if (fprintf(f->file, "%s = %i\n", item->name, *(item->val_int)) < 0) return -4; break; case ITEM_UINT: if (fprintf(f->file, "%s = %d\n", item->name, *(item->val_uint)) < 0) return -5; break; case ITEM_BOOL: if (fprintf(f->file, "%s = %s\n", item->name, *(item->val_bool) ? "yes" : "no") < 0) return -6; break; case ITEM_SECTION: { int res; if (fprintf(f->file, "%s = {\n", item->name) < 0) return -7; res = th_cfg_write_sect(f, (cfgitem_t *) item->data, nesting + 1); if (res != 0) return res; if (fprintf(f->file, "}\n\n") < 0) return -8; } break; case ITEM_HEX_TRIPLET: if (fprintf(f->file, "%s = \"%06x\"\n", item->name, *((int *) item->data)) < 0) return -6; break; } } item = item->next; } return 0; } int th_cfg_write(FILE *outFile, char *filename, cfgitem_t *cfg) { conffile_t f; if (cfg == NULL) return -1; 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_cfg_write_sect(&f, cfg, 0); }