view th_config.c @ 378:afbc3bfd3e03

Sync th-libs.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 03 Oct 2011 00:31:46 +0300
parents e694c02d6982
children
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, ",\n");
                            th_print_indent(f, nesting);
                        }
                        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);
}