Mercurial > hg > th-libs
view th_config.c @ 638:c4bca120bfb0
Cleanups.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Tue, 21 Jan 2020 12:23:59 +0200 |
parents | 7dce38c022d7 |
children | ae601363fdad |
line wrap: on
line source
/* * Very simple configuration handling functions * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2004-2020 Tecnic Software productions (TNSP) * * Please read file 'COPYING' for information on license and distribution. */ #include "th_util.h" #include "th_config.h" #include "th_string.h" #include <stdio.h> #include <stdarg.h> #define SET_MAX_BUF (8192) /* Free a given configuration (the values are not free'd) */ void th_cfg_free(th_cfgitem_t *cfg) { th_cfgitem_t *node = cfg; while (node != NULL) { th_cfgitem_t *next = (th_cfgitem_t *) node->node.next; if (node->type == ITEM_SECTION) th_cfg_free((th_cfgitem_t *) node->v.data); th_free(node->name); th_free(node); node = next; } } /* Allocate and add new item to configuration */ static th_cfgitem_t *th_cfg_add(th_cfgitem_t **cfg, const char *name, const int type, void *data) { th_cfgitem_t *node; if (cfg == NULL) return NULL; // Allocate new item if ((node = (th_cfgitem_t *) th_malloc0(sizeof(th_cfgitem_t))) == NULL) return NULL; // Set values node->type = type; node->v.data = data; node->name = th_strdup(name); // Insert into linked list th_llist_append_node((th_llist_t **) cfg, (th_llist_t *) node); return node; } /* Add integer type setting into give configuration */ int th_cfg_add_int(th_cfgitem_t **cfg, const char *name, int *itemData, int defValue) { th_cfgitem_t *node = th_cfg_add(cfg, name, ITEM_INT, (void *) itemData); if (node == NULL) return THERR_MALLOC; *itemData = defValue; return THERR_OK; } int th_cfg_add_hexvalue(th_cfgitem_t **cfg, const char *name, unsigned int *itemData, unsigned int defValue) { th_cfgitem_t *node = th_cfg_add(cfg, name, ITEM_HEX_TRIPLET, (void *) itemData); if (node == NULL) return THERR_MALLOC; *itemData = defValue; return THERR_OK; } /* Add unsigned integer type setting into give configuration */ int th_cfg_add_uint(th_cfgitem_t **cfg, const char *name, unsigned int *itemData, unsigned int defValue) { th_cfgitem_t *node = th_cfg_add(cfg, name, ITEM_UINT, (void *) itemData); if (node == NULL) return THERR_MALLOC; *itemData = defValue; return THERR_OK; } /* Add strint type setting into given configuration */ int th_cfg_add_string(th_cfgitem_t **cfg, const char *name, char **itemData, char *defValue) { th_cfgitem_t *node = th_cfg_add(cfg, name, ITEM_STRING, (void *) itemData); if (node == NULL) return THERR_MALLOC; *itemData = th_strdup(defValue); return THERR_OK; } /* Add boolean type setting into given configuration */ int th_cfg_add_bool(th_cfgitem_t **cfg, const char *name, BOOL *itemData, BOOL defValue) { th_cfgitem_t *node = th_cfg_add(cfg, name, ITEM_BOOL, (void *) itemData); if (node == NULL) return THERR_MALLOC; *itemData = defValue; return THERR_OK; } /* Add implicit comment */ int th_cfg_add_comment(th_cfgitem_t **cfg, const char *comment) { th_cfgitem_t *node = th_cfg_add(cfg, comment, ITEM_COMMENT, NULL); if (node == NULL) return THERR_MALLOC; return THERR_OK; } /* Add new section */ int th_cfg_add_section(th_cfgitem_t **cfg, const char *name, th_cfgitem_t *sect) { th_cfgitem_t *node = th_cfg_add(cfg, name, ITEM_SECTION, (void *) sect); if (node == NULL) return THERR_MALLOC; return THERR_OK; } int th_cfg_add_string_list(th_cfgitem_t **cfg, const char *name, th_llist_t **data) { th_cfgitem_t *node; if (data == NULL) return THERR_NULLPTR; if ((node = th_cfg_add(cfg, name, ITEM_STRING_LIST, (void *) data)) == NULL) return THERR_MALLOC; return THERR_OK; } /* Read a given file into configuration structure and variables */ enum { PM_EOF, PM_ERROR, PM_IDLE, PM_COMMENT, PM_NEXT, PM_KEYNAME, PM_KEYSET, PM_STRING, PM_NUMERIC, PM_BOOL, PM_SECTION, PM_LIST, PM_LAST }; typedef struct { int ch, strDelim, prevMode, nextMode, parseMode; } th_cfgparserctx_t; static void th_cfg_set_parsemode(th_cfgparserctx_t *ctx, const int mode) { ctx->prevMode = ctx->parseMode; ctx->parseMode = mode; } static void th_cfg_set_next_parsemode(th_cfgparserctx_t *ctx, const int mode) { th_cfg_set_parsemode(ctx, PM_NEXT); ctx->nextMode = mode; } #define VADDCH(ch) if (strPos < SET_MAX_BUF) { tmpStr[strPos++] = ch; } static BOOL th_cfg_is_end(const int ch) { return ch == '\r' || ch == '\n' || ch == ';' || ch == '#' || th_isspace(ch); } static int th_cfg_get_parsemode(const int type) { switch (type) { case ITEM_HEX_TRIPLET: case ITEM_STRING: return PM_STRING; case ITEM_INT: case ITEM_UINT: case ITEM_FLOAT: return PM_NUMERIC; case ITEM_BOOL: return PM_BOOL; case ITEM_SECTION: return PM_SECTION; case ITEM_STRING_LIST: return PM_LIST; default: return PM_ERROR; } } static int th_cfg_set_item(th_cfgparserctx_t *ctx, th_cfgitem_t *item, const char *str) { BOOL res = TRUE; switch (item->type) { case ITEM_HEX_TRIPLET: res = th_get_hex_triplet(str, item->v.val_uint); break; case ITEM_STRING: th_pstr_cpy(item->v.val_str, str); break; case ITEM_STRING_LIST: { char *tmp; if ((tmp = th_strdup(str)) != NULL) { th_llist_append(item->v.list, tmp); th_cfg_set_next_parsemode(ctx, PM_LIST); // Early exit as we set the parsemode here return THERR_OK; } else res = FALSE; } break; case ITEM_INT: *(item->v.val_int) = atoi(str); break; case ITEM_UINT: *(item->v.val_uint) = atol(str); break; case ITEM_FLOAT: *(item->v.val_float) = atof(str); break; case ITEM_BOOL: res = th_get_boolean(str, item->v.val_bool); break; default: res = FALSE; break; } th_cfg_set_parsemode(ctx, PM_IDLE); return res ? THERR_OK : THERR_INVALID_DATA; } static int th_cfg_read_sect(th_ioctx *fh, th_cfgitem_t *sect, int nesting) { th_cfgparserctx_t ctx; th_cfgitem_t *item = NULL; char *tmpStr = NULL; size_t strPos; BOOL isEscaped, isStart, fpSet; int ret = THERR_OK; // Initialize values memset(&ctx, 0, sizeof(ctx)); ctx.ch = -1; ctx.nextMode = ctx.prevMode = ctx.parseMode = PM_IDLE; isEscaped = fpSet = isStart = FALSE; strPos = 0; if ((tmpStr = th_malloc(SET_MAX_BUF + 1)) == NULL) goto out; // Parse the configuration while (ctx.parseMode != PM_EOF && ctx.parseMode != PM_ERROR) { if (ctx.ch == -1) { // Get next character switch (ctx.ch = thfgetc(fh)) { case EOF: if (ctx.parseMode != PM_IDLE) { ret = th_io_error(fh, THERR_OUT_OF_DATA, "Unexpected end of file."); goto out; } ctx.parseMode = PM_EOF; break; case '\n': fh->line++; } } switch (ctx.parseMode) { case PM_COMMENT: // Comment parsing mode if (ctx.ch == '\n') { // End of line, end of comment th_cfg_set_parsemode(&ctx, ctx.prevMode); } ctx.ch = -1; break; case PM_IDLE: // Normal parsing mode if (ctx.ch == '#') { th_cfg_set_parsemode(&ctx, PM_COMMENT); ctx.ch = -1; } else if (th_cfg_is_end(ctx.ch)) { ctx.ch = -1; } else if (ctx.ch == '}') { if (nesting > 0) { // Check for validation errors goto out; } else { ret = th_io_error(fh, THERR_INVALID_DATA, "Invalid nesting sequence encountered."); goto out; } } else if (th_isalpha(ctx.ch)) { // Start of key name found th_cfg_set_parsemode(&ctx, PM_KEYNAME); strPos = 0; } else { // Error! Invalid character found ret = th_io_error(fh, THERR_INVALID_DATA, "Unexpected character '%c'.", ctx.ch); goto out; } break; case PM_KEYNAME: // Configuration KEY name parsing mode if (ctx.ch == '#') { // Start of comment th_cfg_set_parsemode(&ctx, PM_COMMENT); ctx.ch = -1; } else if (th_iscrlf(ctx.ch) || th_isspace(ctx.ch) || ctx.ch == '=') { // End of key name th_cfg_set_next_parsemode(&ctx, PM_KEYSET); } else if (th_isalnum(ctx.ch) || ctx.ch == '_' || ctx.ch == '-') { // Add to key name string VADDCH(ctx.ch) else { // Error! Key name string too long! ret = th_io_error(fh, THERR_INVALID_DATA, "Config key name too long!"); goto out; } ctx.ch = -1; tmpStr[strPos] = 0; } else { // Error! Invalid character found tmpStr[strPos] = 0; ret = th_io_error(fh, THERR_INVALID_DATA, "Unexpected character '%c' in key name '%s'.", ctx.ch, tmpStr); goto out; } break; case PM_KEYSET: if (ctx.ch == '=') { // Find key from configuration BOOL found; tmpStr[strPos] = 0; for (item = sect, found = FALSE; item != NULL && !found; ) { if (item->type != ITEM_COMMENT && item->name != NULL && strcmp(item->name, tmpStr) == 0) found = TRUE; else item = (th_cfgitem_t *) item->node.next; } // Check if key was found if (found) { // Okay, set next mode th_cfg_set_next_parsemode(&ctx, th_cfg_get_parsemode(item->type)); isStart = TRUE; fpSet = FALSE; strPos = 0; } else { // Error! No configuration key by this name found ret = th_io_error(fh, THERR_INVALID_DATA, "No such configuration setting: '%s'.", tmpStr); goto out; } ctx.ch = -1; } else { // Error! '=' expected! ret = th_io_error(fh, THERR_INVALID_DATA, "Unexpected character '%c', assignation '=' was expected.", ctx.ch); goto out; } break; case PM_NEXT: // Search next item parsing mode if (ctx.ch == '#') { // Start of comment th_cfg_set_parsemode(&ctx, PM_COMMENT); } else if (th_isspace(ctx.ch) || th_iscrlf(ctx.ch)) { // Ignore whitespaces and linechanges ctx.ch = -1; } else { // Next item found th_cfg_set_parsemode(&ctx, ctx.nextMode); } break; case PM_LIST: if (isStart) { th_cfg_set_parsemode(&ctx, PM_STRING); } else if (ctx.ch == ',') { th_cfg_set_next_parsemode(&ctx, PM_STRING); ctx.ch = -1; isStart = TRUE; } else { th_cfg_set_parsemode(&ctx, PM_IDLE); } break; case PM_SECTION: // Section parsing mode if (ctx.ch != '{') { // Error! Section start '{' expected! ret = th_io_error(fh, THERR_INVALID_DATA, "Unexpected character '%c', section start '{' was expected.", ctx.ch); goto out; } else { if ((ret = th_cfg_read_sect(fh, item->v.section, nesting + 1)) != THERR_OK) goto out; th_cfg_set_parsemode(&ctx, PM_IDLE); ctx.ch = -1; } break; case PM_STRING: // String parsing mode if (isStart) { // Start of string, get delimiter ctx.strDelim = ctx.ch; isStart = FALSE; strPos = 0; isEscaped = FALSE; } else if (!isEscaped && ctx.ch == ctx.strDelim) { // End of string, set the value tmpStr[strPos] = 0; if ((ret = th_cfg_set_item(&ctx, item, tmpStr)) != THERR_OK) goto out; } else if (!isEscaped && ctx.ch == '\\') { // Escape sequence isEscaped = TRUE; } else { // Add character to string VADDCH(ctx.ch) else { // Error! String too long! ret = th_io_error(fh, THERR_INVALID_DATA, "String too long! Maximum is %d characters.", SET_MAX_BUF); goto out; } isEscaped = FALSE; } ctx.ch = -1; break; case PM_NUMERIC: // Integer parsing mode if (isStart && item->type == ITEM_UINT && ctx.ch == '-') { // Error! Negative values not allowed for unsigned ints ret = th_io_error(fh, THERR_INVALID_DATA, "Negative value specified for %s, unsigned value expected.", item->name); goto out; } else if (isStart && (ctx.ch == '-' || ctx.ch == '+')) { VADDCH(ctx.ch) else ret = THERR_INVALID_DATA; } else if (isStart && item->type == ITEM_FLOAT && ctx.ch == '.') { fpSet = TRUE; VADDCH('0') else ret = THERR_INVALID_DATA; VADDCH(ctx.ch) else ret = THERR_INVALID_DATA; } else if (item->type == ITEM_FLOAT && ctx.ch == '.' && !fpSet) { fpSet = TRUE; VADDCH(ctx.ch) else ret = THERR_INVALID_DATA; } else if (th_isdigit(ctx.ch)) { VADDCH(ctx.ch) else ret = THERR_INVALID_DATA; } else if (th_cfg_is_end(ctx.ch)) { // End of integer parsing mode tmpStr[strPos] = 0; if ((ret = th_cfg_set_item(&ctx, item, tmpStr)) != THERR_OK) goto out; th_cfg_set_parsemode(&ctx, PM_IDLE); } else { // Error! Unexpected character. ret = th_io_error(fh, THERR_INVALID_DATA, "Unexpected character '%c' for numeric setting '%s'.", ctx.ch, item->name); goto out; } if (ret != THERR_OK) { // Error! String too long! ret = th_io_error(fh, ret, "String too long! Maximum is %d characters.", SET_MAX_BUF); goto out; } isStart = FALSE; ctx.ch = -1; break; case PM_BOOL: // Boolean parsing mode if (isStart) { isStart = FALSE; strPos = 0; } if (th_isalnum(ctx.ch)) { VADDCH(ctx.ch) else ret = THERR_INVALID_DATA; } else if (th_cfg_is_end(ctx.ch)) { tmpStr[strPos] = 0; ret = th_cfg_set_item(&ctx, item, tmpStr); } if (ret != THERR_OK) { ret = th_io_error(fh, ret, "Invalid boolean value for '%s'.", item->name); goto out; } ctx.ch = -1; break; } } out: th_free(tmpStr); // Return result if (ret == THERR_OK && ctx.parseMode == PM_ERROR) ret = THERR_INVALID_DATA; return ret; } int th_cfg_read(th_ioctx *fh, th_cfgitem_t *cfg) { if (fh == NULL || cfg == NULL) return THERR_NULLPTR; return th_cfg_read_sect(fh, cfg, 0); } /* Write a configuration into file */ static BOOL th_print_indent_a(th_ioctx *fh, const int nesting, const char *fmt, va_list ap) { for (int i = 0; i < nesting * 4; i++) { if (thfputc(' ', fh) == EOF) return FALSE; } return thvfprintf(fh, fmt, ap) >= 0; } static BOOL th_print_indent(th_ioctx *fh, const int nesting, const char *fmt, ...) { BOOL ret; va_list ap; va_start(ap, fmt); ret = th_print_indent_a(fh, nesting, fmt, ap); va_end(ap); return ret; } static BOOL th_cfg_is_item_valid(const th_cfgitem_t *item) { switch (item->type) { case ITEM_STRING: return (*(item->v.val_str) != NULL); case ITEM_STRING_LIST: return (*(item->v.list) != NULL); case ITEM_SECTION: return TRUE; case ITEM_INT: case ITEM_UINT: case ITEM_FLOAT: case ITEM_BOOL: case ITEM_HEX_TRIPLET: return TRUE; default: return FALSE; } } static BOOL th_cfg_write_string_escaped(th_ioctx *fh, const char *str, const char delim) { for (const char *ptr = str; *ptr; ptr++) { if (*ptr == delim) { if (thfputc('\\', fh) == EOF || thfputc(*ptr, fh) == EOF) return FALSE; } else { if (thfputc(*ptr, fh) == EOF) return FALSE; } } return TRUE; } static int th_cfg_write_item(th_ioctx *fh, const th_cfgitem_t *item) { switch (item->type) { case ITEM_STRING: if (thfputc('"', fh) != EOF && th_cfg_write_string_escaped(fh, *(item->v.val_str), '"') && thfputc('"', fh) != EOF) return 1; else return -1; case ITEM_INT: return thfprintf(fh, "%d", *(item->v.val_int)); case ITEM_UINT: return thfprintf(fh, "%u", *(item->v.val_uint)); case ITEM_FLOAT: return thfprintf(fh, "%1.5f", *(item->v.val_float)); case ITEM_BOOL: return thfprintf(fh, "%s", *(item->v.val_bool) ? "yes" : "no"); case ITEM_HEX_TRIPLET: return thfprintf(fh, "\"%06x\"", *(item->v.val_int)); default: return -1; } } static int th_cfg_write_sect(th_ioctx *fh, const th_cfgitem_t *item, const int nesting) { while (item != NULL) { if (item->name == NULL) return THERR_NULLPTR; if (item->type == ITEM_COMMENT) { BOOL lineStart = TRUE, lineFeed = FALSE; for (const char *ptr = item->name; *ptr; ptr++) switch (*ptr) { case '\r': case '\n': lineStart = lineFeed = TRUE; break; default: if (lineFeed) { thfprintf(fh, "\n"); lineFeed = FALSE; } if (lineStart) { if (!th_print_indent(fh, nesting, "# ")) return THERR_FWRITE; lineStart = FALSE; } if (thfputc(*ptr, fh) == EOF) return THERR_FWRITE; } if (!lineFeed) thfprintf(fh, "\n"); } else if (item->type == ITEM_SECTION) { int res; if (!th_print_indent(fh, nesting, "%s = {\n", item->name)) return THERR_FWRITE; if ((res = th_cfg_write_sect(fh, item->v.section, nesting + 1)) != 0) return res; if (!th_print_indent(fh, nesting, "}\n\n")) return THERR_FWRITE; } else if (item->type == ITEM_STRING_LIST) { if (!th_cfg_is_item_valid(item)) { if (!th_print_indent(fh, nesting, "#%s = \"\", \"\"", item->name)) return THERR_FWRITE; } else { th_llist_t *node = *(item->v.list); size_t n = th_llist_length(node); if (!th_print_indent(fh, nesting, "%s = \n", item->name)) return THERR_FWRITE; for (; node != NULL; node = node->next) if (node->data != NULL) { if (!th_print_indent(fh, nesting, "\"") || !th_cfg_write_string_escaped(fh, (char *) node->data, '"') || thfprintf(fh, "\"%s\n", (--n > 0) ? "," : "") < 0) return THERR_FWRITE; } if (!th_print_indent(fh, nesting, "\n")) return THERR_FWRITE; } } else { if (!th_print_indent(fh, nesting, "%s%s = ", th_cfg_is_item_valid(item) ? "" : "#", item->name) || th_cfg_write_item(fh, item) < 0 || thfprintf(fh, "\n") < 0) return THERR_FWRITE; } item = (th_cfgitem_t *) item->node.next; } return THERR_OK; } int th_cfg_write(th_ioctx *fh, const th_cfgitem_t *cfg) { if (fh == NULL || cfg == NULL) return THERR_NULLPTR; thfprintf(fh, "# Configuration written by %s %s\n\n", th_prog_desc, th_prog_version); return th_cfg_write_sect(fh, cfg, 0); } /* Find a configuration item based on section and/or name (and/or) type. * The first matching item will be returned. */ static th_cfgitem_t *th_cfg_find_do(th_cfgitem_t *item, const char *name, const int type) { while (item != NULL) { BOOL match = TRUE; // Has type check been set, and does it match? if (type != -1 && item->type != type) match = FALSE; // Check item name if (name != NULL && item->name != NULL && strcmp(name, item->name) != 0) match = FALSE; // Recurse to section if (!match && item->type == ITEM_SECTION) { th_cfgitem_t *tmp = th_cfg_find_do(item->v.section, name, type); if (tmp != NULL) return tmp; } // Do we have a match? if (match) return item; item = (th_cfgitem_t *) item->node.next; } return NULL; } th_cfgitem_t *th_cfg_find(th_cfgitem_t *cfg, const char *section, const char *name, const int type) { th_cfgitem_t *node; if (section != NULL) node = th_cfg_find_do(cfg, section, ITEM_SECTION); else node = cfg; return th_cfg_find_do(node, name, type); }