view th_config.c @ 322:b9c15c57dc8f

Clean up message functions, add new printMsgQ() helper function for messages that should not go into the log file. Add skeleton help function, accessible via F1 key. And other cleanups.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 11 Jun 2011 09:48:26 +0300
parents 0db02b8d2d11
children e694c02d6982
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->v.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->v.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, (unsigned int) 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:
                        c = -1;
                        isStart = TRUE;
                        prevMode = parseMode;
                        parseMode = PM_NEXT;
                        nextMode = 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, item->v.section, 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:
                        *(item->v.val_int) = th_get_hex_triplet(tmpStr);
                        prevMode = parseMode;
                        parseMode = PM_NORMAL;
                        break;
                    case ITEM_STRING:
                        th_pstrcpy(item->v.val_str, tmpStr);
                        prevMode = parseMode;
                        parseMode = PM_NORMAL;
                        break;
                    case ITEM_STRING_LIST:
                        th_llist_append(item->v.list, 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:
                    *(item->v.val_int) = atoi(tmpStr);
                    break;

                case ITEM_UINT:
                    *(item->v.val_uint) = 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 = FALSE;
                
                /* 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 {
                    *(item->v.val_bool) = 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->v.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->v.val_str)) < 0)
                        return -3;
                }
                break;

            case ITEM_STRING_LIST:
                if (*(item->v.list) == NULL) {
                    if (fprintf(f->file, "#%s = \"\", \"\"\n", item->name) < 0)
                        return -3;
                } else {
                    qlist_t *node = *(item->v.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->v.val_int)) < 0)
                    return -4;
                break;

            case ITEM_UINT:
                if (fprintf(f->file, "%s = %d\n",
                    item->name, *(item->v.val_uint)) < 0)
                    return -5;
                break;

            case ITEM_BOOL:
                if (fprintf(f->file, "%s = %s\n",
                    item->name, *(item->v.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, item->v.section, 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, *(item->v.val_int)) < 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);
}