mirror of
https://github.com/coop-deluxe/sm64coopdx.git
synced 2026-06-24 16:53:42 +00:00
1224 lines
32 KiB
C
1224 lines
32 KiB
C
/*
|
|
libconfigini - an ini formatted configuration parser library
|
|
Copyright (C) 2013-present Taner YILMAZ <taner44@gmail.com>
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met:
|
|
1. Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
2. Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
3. Neither the name of copyright holders nor the names of its
|
|
contributors may be used to endorse or promote products derived
|
|
from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS
|
|
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
|
|
#include "configini.h"
|
|
#include "queue.h"
|
|
|
|
|
|
#define COMMENT_CHARS "#" /* default comment chars */
|
|
#define KEYVAL_SEP '=' /* default key-val seperator character */
|
|
#define STR_TRUE "1" /* default string valu of true */
|
|
#define STR_FALSE "0" /* default string valu of false */
|
|
|
|
#define CONFIG_INIT_MAGIC 0x12F0ED1
|
|
|
|
#define UNUSED __attribute__((unused))
|
|
|
|
/**
|
|
* \brief Configuration key-value
|
|
*/
|
|
typedef struct ConfigKeyValue
|
|
{
|
|
char *key;
|
|
char *value;
|
|
TAILQ_ENTRY(ConfigKeyValue) next;
|
|
} ConfigKeyValue;
|
|
|
|
/**
|
|
* \brief Configuration section
|
|
*/
|
|
typedef struct ConfigSection
|
|
{
|
|
char *name;
|
|
int numofkv;
|
|
TAILQ_HEAD(, ConfigKeyValue) kv_list;
|
|
TAILQ_ENTRY(ConfigSection) next;
|
|
} ConfigSection;
|
|
|
|
/**
|
|
* \brief Configuration handle
|
|
*/
|
|
struct Config
|
|
{
|
|
char *comment_chars;
|
|
char keyval_sep;
|
|
char *true_str;
|
|
char *false_str;
|
|
int initnum;
|
|
int numofsect;
|
|
TAILQ_HEAD(, ConfigSection) sect_list;
|
|
};
|
|
|
|
|
|
|
|
|
|
static int StrSafeCopy(char *dst, const char *src, int size)
|
|
{
|
|
char *d = dst;
|
|
const char *s = src;
|
|
int n = size;
|
|
|
|
if (n != 0 && --n != 0) {
|
|
do {
|
|
if ((*d++ = *s++) == 0)
|
|
break;
|
|
} while (--n != 0);
|
|
}
|
|
|
|
if (n == 0) {
|
|
if (size != 0)
|
|
*d = '\0';
|
|
while (*s++)
|
|
;
|
|
}
|
|
|
|
return (s - src - 1);
|
|
}
|
|
|
|
static bool StrIsTypeOfTrue(const char *s)
|
|
{
|
|
if ( !strcasecmp(s, "true") || !strcasecmp(s, "yes") || !strcasecmp(s, "1") )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static bool StrIsTypeOfFalse(const char *s)
|
|
{
|
|
if ( !strcasecmp(s, "false") || !strcasecmp(s, "no") || !strcasecmp(s, "0") )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
const char *ConfigRetToString(ConfigRet ret)
|
|
{
|
|
switch(ret) {
|
|
case CONFIG_OK: return "OK";
|
|
case CONFIG_ERR_FILE: return "File IO error";
|
|
case CONFIG_ERR_NO_SECTION: return "No section";
|
|
case CONFIG_ERR_NO_KEY: return "No key";
|
|
case CONFIG_ERR_MEMALLOC: return "Memory allocation failed";
|
|
case CONFIG_ERR_INVALID_PARAM: return "Invalid parameter";
|
|
case CONFIG_ERR_INVALID_VALUE: return "Invalid value";
|
|
case CONFIG_ERR_PARSING: return "Parse error";
|
|
default: return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigSetCommentCharset() sets comment characters
|
|
*
|
|
* \param cfg config handle
|
|
* \param comment_ch charaters to consider as comments
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigSetCommentCharset(Config *cfg, const char *comment_ch)
|
|
{
|
|
char *p;
|
|
|
|
if (!cfg || !comment_ch)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
if ((p = strdup(comment_ch)) == NULL)
|
|
return CONFIG_ERR_MEMALLOC;
|
|
|
|
if (cfg->comment_chars)
|
|
free(cfg->comment_chars);
|
|
cfg->comment_chars = p;
|
|
|
|
return CONFIG_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigSetCommentCharset() sets comment characters
|
|
*
|
|
* \param cfg config handle
|
|
* \param ch charater to consider as key-value seperator
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigSetKeyValSepChar(Config *cfg, char ch)
|
|
{
|
|
if (!cfg)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
cfg->keyval_sep = ch;
|
|
|
|
return CONFIG_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigSetCommentCharset() sets comment characters
|
|
*
|
|
* \param cfg config handle
|
|
* \param true_str string value of boolean true (must be one of these: "true", "yes", "1")
|
|
* \param false_str string value of boolean false (must be one of these: "false", "no", "0")
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigSetBoolString(Config *cfg, const char *true_str, const char *false_str)
|
|
{
|
|
char *t, *f;
|
|
|
|
if ( !cfg ||
|
|
!true_str || !*true_str || !StrIsTypeOfTrue(true_str) ||
|
|
!false_str || !*false_str || !StrIsTypeOfFalse(false_str) )
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
if ((t = strdup(true_str)) == NULL)
|
|
return CONFIG_ERR_MEMALLOC;
|
|
|
|
if ((f = strdup(false_str)) == NULL) {
|
|
free(t);
|
|
return CONFIG_ERR_MEMALLOC;
|
|
}
|
|
|
|
if (cfg->true_str)
|
|
free(cfg->true_str);
|
|
if (cfg->false_str)
|
|
free(cfg->false_str);
|
|
|
|
cfg->true_str = t;
|
|
cfg->false_str = f;
|
|
|
|
return CONFIG_OK;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* \brief ConfigGetSection() gets the requested section
|
|
*
|
|
* \param cfg config handle to search in
|
|
* \param section section name to search for
|
|
* \param sect pointer to ConfigSection* searched for to save
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
static ConfigRet ConfigGetSection(const Config *cfg, const char *section, ConfigSection **sect)
|
|
{
|
|
if (!cfg || !sect)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
TAILQ_FOREACH(*sect, &cfg->sect_list, next) {
|
|
if ( (section && (*sect)->name && !strcmp((*sect)->name, section)) ||
|
|
(!section && !(*sect)->name) ) {
|
|
return CONFIG_OK;
|
|
}
|
|
}
|
|
|
|
return CONFIG_ERR_NO_SECTION;
|
|
}
|
|
|
|
/**
|
|
* \brief Checks whether section exists
|
|
*
|
|
* \param cfg config handle to search in
|
|
* \param section section name to search for
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
bool ConfigHasSection(const Config *cfg, const char *section)
|
|
{
|
|
ConfigSection *sect = NULL;
|
|
|
|
return ( (ConfigGetSection(cfg, section, §) == CONFIG_OK) ? true : false );
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigGetKeyValue() gets the ConfigKeyValue *
|
|
*
|
|
* \param cfg config handle
|
|
* \param sect section to search in
|
|
* \param key key to search for
|
|
* \param kv pointer to ConfigKeyValue* searched for to save
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
static ConfigRet ConfigGetKeyValue(UNUSED const Config *cfg, ConfigSection *sect, const char *key,
|
|
ConfigKeyValue **kv)
|
|
{
|
|
if (!sect || !key || !kv)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
TAILQ_FOREACH(*kv, §->kv_list, next) {
|
|
if (!strcmp((*kv)->key, key))
|
|
return CONFIG_OK;
|
|
}
|
|
|
|
return CONFIG_ERR_NO_KEY;
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigGetSectionCount() gets number of sections
|
|
*
|
|
* \param cfg config handle to search in
|
|
*
|
|
* \return Returns number of sections on success, -1 on failure.
|
|
*/
|
|
int ConfigGetSectionCount(const Config *cfg)
|
|
{
|
|
if (!cfg)
|
|
return -1;
|
|
|
|
return (TAILQ_FIRST(&cfg->sect_list)->numofkv > 0 ? cfg->numofsect : cfg->numofsect - 1);
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigGetKeyCount() gets number of keys
|
|
*
|
|
* \param cfg config handle to search in
|
|
* \param section section name to search for
|
|
*
|
|
* \return Returns number of keys on success, -1 on failure.
|
|
*/
|
|
int ConfigGetKeyCount(const Config *cfg, const char *section)
|
|
{
|
|
ConfigSection *sect = NULL;
|
|
|
|
if (!cfg)
|
|
return -1;
|
|
|
|
if (ConfigGetSection(cfg, section, §) != CONFIG_OK)
|
|
return -1;
|
|
|
|
return sect->numofkv;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* \brief ConfigReadString() reads a string value from the cfg.
|
|
* If any error occurs default value is copied to 'value' buffer and
|
|
* returns reason. If key is optional and does not exists in config,
|
|
* the 'value' is default value and return is CONFIG_ERR_NO_KEY
|
|
*
|
|
* \param cfg config handle
|
|
* \param section section to search in
|
|
* \param key key to search for
|
|
* \param value value to save in
|
|
* \param size value buffer size
|
|
* \param dfl_value default value to copy back if any error occurs
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigReadString(const Config *cfg, const char *section, const char *key,
|
|
char *value, int size, const char *dfl_value)
|
|
{
|
|
ConfigSection *sect = NULL;
|
|
ConfigKeyValue *kv = NULL;
|
|
ConfigRet ret = CONFIG_OK;
|
|
|
|
if (!cfg || !key || !value || (size < 1))
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
*value = '\0';
|
|
|
|
if ( ((ret = ConfigGetSection(cfg, section, §)) != CONFIG_OK) ||
|
|
((ret = ConfigGetKeyValue(cfg, sect, key, &kv)) != CONFIG_OK) ) {
|
|
if (dfl_value)
|
|
StrSafeCopy(value, dfl_value, size);
|
|
return ret;
|
|
}
|
|
|
|
StrSafeCopy(value, kv->value, size);
|
|
|
|
return CONFIG_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigReadInt() reads an integer value from the cfg.
|
|
* If any error occurs default value is copied to 'value' buffer and
|
|
* returns reason. If key is optional and does not exists in config,
|
|
* the 'value' is default value and return is CONFIG_ERR_NO_KEY
|
|
*
|
|
* \param cfg config handle
|
|
* \param section section to search in
|
|
* \param key key to search for
|
|
* \param value value to save in
|
|
* \param dfl_value default value to copy back if any error occurs
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigReadInt(const Config *cfg, const char *section, const char *key,
|
|
int *value, int dfl_value)
|
|
{
|
|
ConfigSection *sect = NULL;
|
|
ConfigKeyValue *kv = NULL;
|
|
ConfigRet ret = CONFIG_OK;
|
|
char *p = NULL;
|
|
|
|
if (!cfg || !key || !value)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
*value = dfl_value;
|
|
|
|
if ( ((ret = ConfigGetSection(cfg, section, §)) != CONFIG_OK) ||
|
|
((ret = ConfigGetKeyValue(cfg, sect, key, &kv)) != CONFIG_OK) ) {
|
|
return ret;
|
|
}
|
|
|
|
*value = (int) strtol(kv->value, &p, 10);
|
|
if (*p || (errno == ERANGE))
|
|
return CONFIG_ERR_INVALID_VALUE;
|
|
|
|
return CONFIG_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigReadUnsignedInt() reads an unsigned integer value from the cfg.
|
|
* If any error occurs default value is copied to 'value' buffer and
|
|
* returns reason. If key is optional and does not exists in config,
|
|
* the 'value' is default value and return is CONFIG_ERR_NO_KEY
|
|
*
|
|
* \param cfg config handle
|
|
* \param section section to search in
|
|
* \param key key to search for
|
|
* \param value value to save in
|
|
* \param dfl_value default value to copy back if any error occurs
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigReadUnsignedInt(const Config *cfg, const char *section, const char *key,
|
|
unsigned int *value, unsigned int dfl_value)
|
|
{
|
|
ConfigSection *sect = NULL;
|
|
ConfigKeyValue *kv = NULL;
|
|
ConfigRet ret = CONFIG_OK;
|
|
char *p = NULL;
|
|
|
|
if (!cfg || !key || !value)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
*value = dfl_value;
|
|
|
|
if ( ((ret = ConfigGetSection(cfg, section, §)) != CONFIG_OK) ||
|
|
((ret = ConfigGetKeyValue(cfg, sect, key, &kv)) != CONFIG_OK) ) {
|
|
return ret;
|
|
}
|
|
|
|
*value = (unsigned int) strtoul(kv->value, &p, 10);
|
|
if (*p || (errno == ERANGE))
|
|
return CONFIG_ERR_INVALID_VALUE;
|
|
|
|
return CONFIG_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigReadFloat() reads a float value from the cfg.
|
|
* If any error occurs default value is copied to 'value' buffer and
|
|
* returns reason. If key is optional and does not exists in config,
|
|
* the 'value' is default value and return is CONFIG_ERR_NO_KEY
|
|
*
|
|
* \param cfg config handle
|
|
* \param section section to search in
|
|
* \param key key to search for
|
|
* \param value value to save in
|
|
* \param dfl_value default value to copy back if any error occurs
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigReadFloat(const Config *cfg, const char *section, const char *key,
|
|
float *value, float dfl_value)
|
|
{
|
|
ConfigSection *sect = NULL;
|
|
ConfigKeyValue *kv = NULL;
|
|
ConfigRet ret = CONFIG_OK;
|
|
char *p = NULL;
|
|
|
|
if (!cfg || !key || !value)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
*value = dfl_value;
|
|
|
|
if ( ((ret = ConfigGetSection(cfg, section, §)) != CONFIG_OK) ||
|
|
((ret = ConfigGetKeyValue(cfg, sect, key, &kv)) != CONFIG_OK) ) {
|
|
return ret;
|
|
}
|
|
|
|
*value = strtof(kv->value, &p);
|
|
if (*p || (errno == ERANGE))
|
|
return CONFIG_ERR_INVALID_VALUE;
|
|
|
|
return CONFIG_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigReadDouble() reads a double value from the cfg.
|
|
* If any error occurs default value is copied to 'value' buffer and
|
|
* returns reason. If key is optional and does not exists in config,
|
|
* the 'value' is default value and return is CONFIG_ERR_NO_KEY
|
|
*
|
|
* \param cfg config handle
|
|
* \param section section to search in
|
|
* \param key key to search for
|
|
* \param value value to save in
|
|
* \param dfl_value default value to copy back if any error occurs
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigReadDouble(const Config *cfg, const char *section, const char *key,
|
|
double *value, double dfl_value)
|
|
{
|
|
ConfigSection *sect = NULL;
|
|
ConfigKeyValue *kv = NULL;
|
|
ConfigRet ret = CONFIG_OK;
|
|
char *p = NULL;
|
|
|
|
if (!cfg || !key || !value)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
*value = dfl_value;
|
|
|
|
if ( ((ret = ConfigGetSection(cfg, section, §)) != CONFIG_OK) ||
|
|
((ret = ConfigGetKeyValue(cfg, sect, key, &kv)) != CONFIG_OK) ) {
|
|
return ret;
|
|
}
|
|
|
|
*value = strtod(kv->value, &p);
|
|
if (*p || (errno == ERANGE))
|
|
return CONFIG_ERR_INVALID_VALUE;
|
|
|
|
return CONFIG_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigReadBool() reads a boolean value from the cfg.
|
|
* If any error occurs default value is copied to 'value' buffer and
|
|
* returns reason. If key is optional and does not exists in config,
|
|
* the 'value' is default value and return is CONFIG_ERR_NO_KEY
|
|
*
|
|
* \param cfg config handle
|
|
* \param section section to search in
|
|
* \param key key to search for
|
|
* \param value value to save in
|
|
* \param dfl_value default value to copy back if any error occurs
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigReadBool(const Config *cfg, const char *section, const char *key,
|
|
bool *value, bool dfl_value)
|
|
{
|
|
ConfigSection *sect = NULL;
|
|
ConfigKeyValue *kv = NULL;
|
|
ConfigRet ret = CONFIG_OK;
|
|
|
|
if (!cfg || !key || !value)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
*value = dfl_value;
|
|
|
|
if ( ((ret = ConfigGetSection(cfg, section, §)) != CONFIG_OK) ||
|
|
((ret = ConfigGetKeyValue(cfg, sect, key, &kv)) != CONFIG_OK) ) {
|
|
return ret;
|
|
}
|
|
|
|
if (StrIsTypeOfTrue(kv->value))
|
|
*value = true;
|
|
else if (StrIsTypeOfFalse(kv->value))
|
|
*value = false;
|
|
else
|
|
return CONFIG_ERR_INVALID_VALUE;
|
|
|
|
return CONFIG_OK;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* \brief ConfigAddSection() creates a section in the cfg
|
|
*
|
|
* \param cfg config handle
|
|
* \param section section to add
|
|
* \param sect pointer to added ConfigSection* or NULL if not needed
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
static ConfigRet ConfigAddSection(Config *cfg, const char *section, ConfigSection **sect)
|
|
{
|
|
ConfigSection *_sect = NULL;
|
|
ConfigRet ret = CONFIG_OK;
|
|
|
|
if (!cfg)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
if (!sect)
|
|
sect = &_sect;
|
|
|
|
if ((ret = ConfigGetSection(cfg, section, sect)) != CONFIG_ERR_NO_SECTION)
|
|
return ret;
|
|
|
|
*sect = calloc(1, sizeof(ConfigSection));
|
|
if (*sect == NULL)
|
|
return CONFIG_ERR_MEMALLOC;
|
|
|
|
if (section) {
|
|
if (((*sect)->name = strdup(section)) == NULL) {
|
|
free(*sect);
|
|
return CONFIG_ERR_MEMALLOC;
|
|
}
|
|
}
|
|
|
|
TAILQ_INIT(&(*sect)->kv_list);
|
|
TAILQ_INSERT_TAIL(&cfg->sect_list, *sect, next);
|
|
++(cfg->numofsect);
|
|
|
|
return CONFIG_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigAddString() adds the key with string value to the cfg
|
|
*
|
|
* \param cfg config handle
|
|
* \param section section to add in
|
|
* \param key key to save as
|
|
* \param value value to save as
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigAddString(Config *cfg, const char *section, const char *key, const char *value)
|
|
{
|
|
ConfigSection *sect = NULL;
|
|
ConfigKeyValue *kv = NULL;
|
|
ConfigRet ret = CONFIG_OK;
|
|
const char *p = NULL;
|
|
const char *q = NULL;
|
|
|
|
if (!cfg || !key || !value)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
if ((ret = ConfigAddSection(cfg, section, §)) != CONFIG_OK)
|
|
return ret;
|
|
|
|
switch (ret = ConfigGetKeyValue(cfg, sect, key, &kv)) {
|
|
case CONFIG_OK:
|
|
if (kv->value) {
|
|
free(kv->value);
|
|
kv->value = NULL;
|
|
}
|
|
break;
|
|
|
|
case CONFIG_ERR_NO_KEY:
|
|
if ((kv = calloc(1, sizeof(ConfigKeyValue))) == NULL)
|
|
return CONFIG_ERR_MEMALLOC;
|
|
if ((kv->key = strdup(key)) == NULL) {
|
|
free(kv);
|
|
return CONFIG_ERR_MEMALLOC;
|
|
}
|
|
TAILQ_INSERT_TAIL(§->kv_list, kv, next);
|
|
++(sect->numofkv);
|
|
break;
|
|
|
|
default:
|
|
return ret;
|
|
}
|
|
|
|
for (p = value; *p && isspace(*p); ++p)
|
|
;
|
|
for (q = p; *q && (*q != '\r') && (*q != '\n') && !strchr(cfg->comment_chars, *q); ++q)
|
|
;
|
|
while (*q && (q > p) && isspace(*(q - 1)))
|
|
--q;
|
|
|
|
kv->value = (char *) malloc(q - p + 1);
|
|
if (kv->value == NULL) {
|
|
TAILQ_REMOVE(§->kv_list, kv, next);
|
|
--(sect->numofkv);
|
|
free(kv->key);
|
|
free(kv);
|
|
return CONFIG_ERR_MEMALLOC;
|
|
}
|
|
memcpy(kv->value, p, q - p);
|
|
kv->value[q - p] = '\0';
|
|
|
|
return CONFIG_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigAddInt() adds the key with integer value to the cfg
|
|
*
|
|
* \param cfg config handle
|
|
* \param section section to add in
|
|
* \param key key to save as
|
|
* \param value value to save as
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigAddInt(Config *cfg, const char *section, const char *key, int value)
|
|
{
|
|
char buf[64];
|
|
|
|
snprintf(buf, sizeof(buf), "%d", value);
|
|
|
|
return ConfigAddString(cfg, section, key, buf);
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigAddUnsignedInt() adds the key with unsigned integer value to the cfg
|
|
*
|
|
* \param cfg config handle
|
|
* \param section section to add in
|
|
* \param key key to save as
|
|
* \param value value to save as
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigAddUnsignedInt(Config *cfg, const char *section, const char *key, unsigned int value)
|
|
{
|
|
char buf[64];
|
|
|
|
snprintf(buf, sizeof(buf), "%u", value);
|
|
|
|
return ConfigAddString(cfg, section, key, buf);
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigAddFloat() adds the key with float value to the cfg
|
|
*
|
|
* \param cfg config handle
|
|
* \param section section to add in
|
|
* \param key key to save as
|
|
* \param value value to save as
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigAddFloat(Config * cfg, const char *section, const char *key, float value)
|
|
{
|
|
char buf[64];
|
|
|
|
snprintf(buf, sizeof(buf), "%f", value);
|
|
|
|
return ConfigAddString(cfg, section, key, buf);
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigAddDouble() adds the key with double value to the cfg
|
|
*
|
|
* \param cfg config handle
|
|
* \param section section to add in
|
|
* \param key key to save as
|
|
* \param value value to save as
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigAddDouble(Config *cfg, const char *section, const char *key, double value)
|
|
{
|
|
char buf[64];
|
|
|
|
snprintf(buf, sizeof(buf), "%f", value);
|
|
|
|
return ConfigAddString(cfg, section, key, buf);
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigAddBool() adds the key with blooean value to the cfg
|
|
*
|
|
* \param cfg config handle
|
|
* \param section section to add in
|
|
* \param key key to save as
|
|
* \param value value to save as
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigAddBool(Config * cfg, const char *section, const char *key, bool value)
|
|
{
|
|
return ConfigAddString(cfg, section, key, value ? cfg->true_str : cfg->false_str);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
static void _ConfigRemoveKey(ConfigSection *sect, ConfigKeyValue *kv)
|
|
{
|
|
TAILQ_REMOVE(§->kv_list, kv, next);
|
|
--(sect->numofkv);
|
|
|
|
if (kv->key)
|
|
free(kv->key);
|
|
if (kv->value)
|
|
free(kv->value);
|
|
free(kv);
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigRemoveKey() removes the key which exists under section from the cfg
|
|
*
|
|
* \param cfg config handle
|
|
* \param section section to seach in
|
|
* \param key key to remove
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigRemoveKey(Config *cfg, const char *section, const char *key)
|
|
{
|
|
ConfigSection *sect = NULL;
|
|
ConfigKeyValue *kv = NULL;
|
|
ConfigRet ret = CONFIG_OK;
|
|
|
|
if (!cfg || !key)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
if ((ret = ConfigGetSection(cfg, section, §)) == CONFIG_OK) {
|
|
if ((ret = ConfigGetKeyValue(cfg, sect, key, &kv)) == CONFIG_OK)
|
|
_ConfigRemoveKey(sect, kv);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void _ConfigRemoveSection(Config *cfg, ConfigSection *sect)
|
|
{
|
|
ConfigKeyValue *kv, *t_kv;
|
|
|
|
if (!cfg || !sect)
|
|
return;
|
|
|
|
TAILQ_REMOVE(&cfg->sect_list, sect, next);
|
|
--(cfg->numofsect);
|
|
|
|
TAILQ_FOREACH_SAFE(kv, §->kv_list, next, t_kv) {
|
|
_ConfigRemoveKey(sect, kv);
|
|
}
|
|
|
|
if (sect->name)
|
|
free(sect->name);
|
|
free(sect);
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigRemoveSection() removes section from the cfgfile
|
|
*
|
|
* \param cfg config handle
|
|
* \param section section to remove
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigRemoveSection(Config *cfg, const char *section)
|
|
{
|
|
ConfigSection *sect = NULL;
|
|
ConfigRet ret = CONFIG_OK;
|
|
|
|
if (!cfg)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
if ((ret = ConfigGetSection(cfg, section, §)) == CONFIG_OK)
|
|
_ConfigRemoveSection(cfg, sect);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigNew() creates a cfg handle with
|
|
* default section which has no section name
|
|
*
|
|
* \return Config* handle on success, NULL on failure
|
|
*/
|
|
Config *ConfigNew()
|
|
{
|
|
Config *cfg = NULL;
|
|
|
|
cfg = calloc(1, sizeof(Config));
|
|
if (cfg == NULL)
|
|
return NULL;
|
|
|
|
TAILQ_INIT(&cfg->sect_list);
|
|
|
|
/* add default section */
|
|
if (ConfigAddSection(cfg, CONFIG_SECTION_FLAT, NULL) != CONFIG_OK) {
|
|
free(cfg);
|
|
return NULL;
|
|
}
|
|
|
|
cfg->comment_chars = strdup(COMMENT_CHARS);
|
|
cfg->keyval_sep = KEYVAL_SEP;
|
|
cfg->true_str = strdup(STR_TRUE);
|
|
cfg->false_str = strdup(STR_FALSE);
|
|
cfg->initnum = CONFIG_INIT_MAGIC;
|
|
|
|
return cfg;
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigFree() frees the memory for the cfg handle
|
|
*
|
|
* \param cfg config handle
|
|
*/
|
|
void ConfigFree(Config *cfg)
|
|
{
|
|
ConfigSection *sect, *t_sect;
|
|
|
|
if (cfg == NULL)
|
|
return;
|
|
|
|
TAILQ_FOREACH_SAFE(sect, &cfg->sect_list, next, t_sect) {
|
|
_ConfigRemoveSection(cfg, sect);
|
|
}
|
|
|
|
if (cfg->comment_chars) free(cfg->comment_chars);
|
|
if (cfg->true_str) free(cfg->true_str);
|
|
if (cfg->false_str) free(cfg->false_str);
|
|
|
|
free(cfg);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* \brief Gets section name on the buffer p
|
|
*
|
|
* \param cfg config handle
|
|
* \param p read buffer
|
|
* \param section pointer address to section
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
static ConfigRet GetSectName(Config *cfg, char *p, char **section)
|
|
{
|
|
char *q, *r;
|
|
|
|
if (!cfg || !p || !*p || !section)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
*section = NULL;
|
|
|
|
/* get section name */
|
|
while (*p && isspace(*p))
|
|
++p;
|
|
|
|
if (*p != '[')
|
|
return CONFIG_ERR_PARSING;
|
|
|
|
++p;
|
|
while (*p && isspace(*p))
|
|
++p;
|
|
|
|
for (q = p;
|
|
*q && (*q != '\r') && (*q != '\n') && (*q != ']') && !strchr(cfg->comment_chars, *q);
|
|
++q)
|
|
;
|
|
|
|
if (*q != ']')
|
|
return CONFIG_ERR_PARSING;
|
|
|
|
r = q + 1;
|
|
|
|
while (*q && (q > p) && isspace(*(q - 1)))
|
|
--q;
|
|
|
|
if (q == p) /* section has no name */
|
|
return CONFIG_ERR_PARSING;
|
|
|
|
*q = '\0';
|
|
*section = p;
|
|
|
|
/* check rest of section */
|
|
while (*r && isspace(*r))
|
|
++r;
|
|
|
|
/* there are unrecognized trailing data */
|
|
if (*r && !strchr(cfg->comment_chars, *r))
|
|
return CONFIG_ERR_PARSING;
|
|
|
|
return CONFIG_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief Gets key and value on the buffer p
|
|
*
|
|
* \param cfg config handle
|
|
* \param p read buffer
|
|
* \param key pointer address to key
|
|
* \param val pointer address to value
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
static ConfigRet GetKeyVal(Config *cfg, char *p, char **key, char **val)
|
|
{
|
|
char *q, *v;
|
|
|
|
if (!cfg || !p || !*p || !key || !val)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
*key = *val = NULL;
|
|
|
|
/* get key */
|
|
while (*p && isspace(*p))
|
|
++p;
|
|
|
|
for (q = p;
|
|
*q && (*q != '\r') && (*q != '\n') && (*q != cfg->keyval_sep) && !strchr(cfg->comment_chars, *q);
|
|
++q)
|
|
;
|
|
|
|
if (*q != cfg->keyval_sep)
|
|
return CONFIG_ERR_PARSING;
|
|
|
|
v = q + 1;
|
|
|
|
while (*q && (q > p) && isspace(*(q - 1)))
|
|
--q;
|
|
|
|
if (q == p) /* no key name */
|
|
return CONFIG_ERR_PARSING;
|
|
|
|
*q = '\0';
|
|
*key = p;
|
|
|
|
/* get value */
|
|
while (*v && isspace(*v))
|
|
++v;
|
|
|
|
for (q = v;
|
|
*q && (*q != '\r') && (*q != '\n') && !strchr(cfg->comment_chars, *q);
|
|
++q)
|
|
;
|
|
|
|
while (*q && (q > v) && isspace(*(q - 1)))
|
|
--q;
|
|
|
|
if (q == v) /* no value */
|
|
return CONFIG_ERR_INVALID_VALUE;
|
|
|
|
*q = '\0';
|
|
*val = v;
|
|
|
|
return CONFIG_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigRead() reads the stream and populates the entire content to cfg handle
|
|
*
|
|
* \param fp FILE handle to read
|
|
* \param cfg pointer to config handle.
|
|
* If not NULL a handle created with ConfigNew() must be given.
|
|
* If cfg is NULL a new one is created and saved to cfg.
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigRead(FILE *fp, Config **cfg)
|
|
{
|
|
ConfigSection *sect = NULL;
|
|
char *p = NULL;
|
|
char *section = NULL;
|
|
char *key = NULL;
|
|
char *val = NULL;
|
|
char buf[4096];
|
|
Config *_cfg = NULL;
|
|
bool newcfg = false;
|
|
ConfigRet ret = CONFIG_OK;
|
|
|
|
if ( !fp || !cfg || (*cfg && ((*cfg)->initnum != CONFIG_INIT_MAGIC)) )
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
if (*cfg == NULL) {
|
|
_cfg = ConfigNew();
|
|
if (_cfg == NULL)
|
|
return CONFIG_ERR_MEMALLOC;
|
|
*cfg = _cfg;
|
|
newcfg = true;
|
|
}
|
|
else
|
|
_cfg = *cfg;
|
|
|
|
while (!feof(fp)) {
|
|
if (fgets(buf, sizeof(buf), fp) == NULL)
|
|
continue;
|
|
|
|
for (p = buf; *p && isspace(*p) ; ++p)
|
|
;
|
|
if (!*p || strchr(_cfg->comment_chars, *p))
|
|
continue;
|
|
|
|
if (*p == '[') {
|
|
if ((ret = GetSectName(_cfg, p, §ion)) != CONFIG_OK)
|
|
goto error;
|
|
|
|
if ((ret = ConfigAddSection(_cfg, section, §)) != CONFIG_OK)
|
|
goto error;
|
|
}
|
|
else {
|
|
if ((ret = GetKeyVal(_cfg, p, &key, &val)) != CONFIG_OK)
|
|
goto error;
|
|
if (!sect)
|
|
goto error;
|
|
if ((ret = ConfigAddString(_cfg, sect->name, key, val)) != CONFIG_OK)
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
return CONFIG_OK;
|
|
|
|
error:
|
|
if (newcfg) {
|
|
ConfigFree(_cfg);
|
|
*cfg = NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigReadFile() opens and reads the file and populates the
|
|
* entire content to cfg handle
|
|
*
|
|
* \param filename name of file to open and load
|
|
* \param cfg pointer to config handle.
|
|
* If not NULL a handle created with ConfigNew() must be given.
|
|
* If cfg is NULL a new one is created and saved to cfg.
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigReadFile(const char *filename, Config **cfg)
|
|
{
|
|
FILE *fp = NULL;
|
|
ConfigRet ret = CONFIG_OK;
|
|
|
|
if ( !filename || !cfg || (*cfg && ((*cfg)->initnum != CONFIG_INIT_MAGIC)) )
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
if ((fp = fopen(filename, "r")) == NULL)
|
|
return CONFIG_ERR_FILE;
|
|
|
|
ret = ConfigRead(fp, cfg);
|
|
|
|
fclose(fp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigPrint() prints all cfg content to the stream
|
|
*
|
|
* \param cfg config handle
|
|
* \param stream stream to print
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigPrint(const Config *cfg, FILE *stream)
|
|
{
|
|
ConfigSection *sect = NULL;
|
|
ConfigKeyValue *kv = NULL;
|
|
|
|
if (!cfg || !stream)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
TAILQ_FOREACH(sect, &cfg->sect_list, next) {
|
|
if (sect->name)
|
|
fprintf(stream, "[%s]\n", sect->name);
|
|
|
|
TAILQ_FOREACH(kv, §->kv_list, next)
|
|
fprintf(stream, "%s=%s\n", kv->key, kv->value);
|
|
|
|
fprintf(stream, "\n");
|
|
}
|
|
|
|
return CONFIG_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigPrintToFile() prints (saves) all cfg content to the file
|
|
*
|
|
* \param cfg config handle
|
|
* \param filename filename to save in
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigPrintToFile(const Config *cfg, char *filename)
|
|
{
|
|
FILE *fp = NULL;
|
|
ConfigRet ret = CONFIG_OK;
|
|
|
|
if (!cfg || !filename)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
if ((fp = fopen(filename, "wb")) == NULL)
|
|
return CONFIG_ERR_FILE;
|
|
|
|
ret = ConfigPrint(cfg, fp);
|
|
|
|
fclose(fp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* \brief ConfigPrintSettings() prints settings to the stream
|
|
*
|
|
* \param cfg config handle
|
|
* \param stream stream to print
|
|
*
|
|
* \return Returns CONFIG_RET_OK as success, otherwise is an error.
|
|
*/
|
|
ConfigRet ConfigPrintSettings(const Config *cfg, FILE *stream)
|
|
{
|
|
if (!cfg || !stream)
|
|
return CONFIG_ERR_INVALID_PARAM;
|
|
|
|
fprintf(stream, "\n");
|
|
fprintf(stream, "Configuration settings: \n");
|
|
fprintf(stream, " Comment characters : %s\n", cfg->comment_chars);
|
|
fprintf(stream, " Key-Value seperator: %c\n", cfg->keyval_sep);
|
|
fprintf(stream, " True-False strings : %s-%s\n", cfg->true_str, cfg->false_str);
|
|
fprintf(stream, "\n");
|
|
|
|
return CONFIG_OK;
|
|
}
|
|
|