代码拉取完成,页面将自动刷新
/*! \file config.c
* \author Lorenzo Miniero <lorenzo@meetecho.com>
* \copyright GNU General Public License v3
* \brief Configuration files parsing
* \details Implementation of a parser of INI and libconfig configuration files.
*
* \ingroup core
* \ref core
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <libgen.h>
#include <libconfig.h>
#include "config.h"
#include "debug.h"
#include "utils.h"
/* Filename helper */
static char *get_filename(char *path) {
return basename(path);
}
static gboolean is_jcfg_config(const char *path) {
if(path == NULL)
return FALSE;
return strstr(path, ".jcfg") != NULL;
}
/* Trimming helper */
static char *ltrim(char *s) {
if(strlen(s) == 0)
return s;
while(isspace(*s))
s++;
return s;
}
static char *rtrim(char *s) {
if(strlen(s) == 0)
return s;
char *back = s + strlen(s);
while(isspace(*--back));
*(back+1) = '\0';
return s;
}
static char *trim(char *s) {
if(strlen(s) == 0)
return s;
return rtrim(ltrim(s));
}
/* Helper to recursively process a libconfig setting */
static int janus_config_jfcg_parse(janus_config *config, janus_config_container *parent, config_setting_t *setting) {
if(!config || !setting)
return -1;
switch(config_setting_type(setting)) {
case CONFIG_TYPE_INT: {
const char *name = config_setting_name(setting);
int val = config_setting_get_int(setting);
char value[50];
g_snprintf(value, sizeof(value), "%d", val);
janus_config_item *item = janus_config_item_create(name, value);
if(janus_config_add(config, parent, item) < 0) {
janus_config_container_destroy(item);
JANUS_LOG(LOG_ERR, "Error adding item %s to %s\n", name, parent ? parent->name : "root");
return -1;
}
break;
}
case CONFIG_TYPE_INT64: {
const char *name = config_setting_name(setting);
long long val = config_setting_get_int64(setting);
char value[50];
g_snprintf(value, sizeof(value), "%lld", val);
janus_config_item *item = janus_config_item_create(name, value);
if(janus_config_add(config, parent, item) < 0) {
janus_config_container_destroy(item);
JANUS_LOG(LOG_ERR, "Error adding item %s to %s\n", name, parent ? parent->name : "root");
return -1;
}
break;
}
case CONFIG_TYPE_FLOAT: {
const char *name = config_setting_name(setting);
double val = config_setting_get_float(setting);
char value[50];
g_snprintf(value, sizeof(value), "%f", val);
janus_config_item *item = janus_config_item_create(name, value);
if(janus_config_add(config, parent, item) < 0) {
janus_config_container_destroy(item);
JANUS_LOG(LOG_ERR, "Error adding item %s to %s\n", name, parent ? parent->name : "root");
return -1;
}
break;
}
case CONFIG_TYPE_STRING: {
const char *name = config_setting_name(setting);
const char *value = config_setting_get_string(setting);
janus_config_item *item = janus_config_item_create(name, value);
if(janus_config_add(config, parent, item) < 0) {
janus_config_container_destroy(item);
JANUS_LOG(LOG_ERR, "Error adding item %s to %s\n", name, parent ? parent->name : "root");
return -1;
}
break;
}
case CONFIG_TYPE_BOOL: {
const char *name = config_setting_name(setting);
gboolean val = config_setting_get_bool(setting);
janus_config_item *item = janus_config_item_create(name, val ? "true" : "false");
if(janus_config_add(config, parent, item) < 0) {
janus_config_container_destroy(item);
JANUS_LOG(LOG_ERR, "Error adding item %s to %s\n", name, parent ? parent->name : "root");
return -1;
}
break;
}
case CONFIG_TYPE_ARRAY:
case CONFIG_TYPE_LIST:
case CONFIG_TYPE_GROUP: {
int num = config_setting_length(setting);
if(num > 0) {
int i=0;
for(i=0; i<num; i++) {
config_setting_t *elem = config_setting_get_elem(setting, i);
if(elem == NULL) {
JANUS_LOG(LOG_WARN, "Couldn't access element #%d of setting '%s'...\n", i, config_setting_name(setting));
continue;
}
const char *name = config_setting_name(elem);
if(config_setting_type(elem) == CONFIG_TYPE_ARRAY || config_setting_type(elem) == CONFIG_TYPE_LIST) {
/* Create an array and parse it */
janus_config_category *cg = janus_config_array_create(name);
if(janus_config_add(config, parent, cg) < 0) {
JANUS_LOG(LOG_ERR, "Error adding array %s to %s\n", name, parent ? parent->name : "root");
janus_config_container_destroy(cg);
return -1;
}
int res = janus_config_jfcg_parse(config, cg, elem);
if(res < 0)
return res;
} else if(config_setting_type(elem) == CONFIG_TYPE_GROUP) {
/* Create a category and parse it */
janus_config_category *cg = janus_config_category_create(name);
if(janus_config_add(config, parent, cg) < 0) {
JANUS_LOG(LOG_ERR, "Error adding category %s to %s\n", name, parent ? parent->name : "root");
janus_config_container_destroy(cg);
return -1;
}
int res = janus_config_jfcg_parse(config, cg, elem);
if(res < 0)
return res;
} else {
int res = janus_config_jfcg_parse(config, parent, elem);
if(res < 0)
return res;
}
}
}
break;
}
default:
break;
}
return 0;
}
/* Parse a configuration file */
janus_config *janus_config_parse(const char *config_file) {
if(config_file == NULL)
return NULL;
char *tmp_filename = g_strdup(config_file);
char *filename = get_filename(tmp_filename);
if(filename == NULL) {
JANUS_LOG(LOG_ERR, "Invalid filename %s\n", config_file);
g_free(tmp_filename);
return NULL;
}
/* Open file */
FILE *file = fopen(config_file, "rt");
if(!file) {
JANUS_LOG(LOG_ERR, " -- Error reading configuration file '%s'... error %d (%s)\n", filename, errno, strerror(errno));
g_free(tmp_filename);
return NULL;
}
/* Create configuration instance */
janus_config *jc = g_malloc0(sizeof(janus_config));
jc->name = g_strdup(filename);
/* Is this a libconfig or INI config file? */
jc->is_jcfg = is_jcfg_config(jc->name);
if(jc->is_jcfg) {
/* Parse with libconfig and not manually */
config_t config;
config_init(&config);
if(config_read(&config, file) == CONFIG_FALSE) {
JANUS_LOG(LOG_ERR, "Error parsing config file at line %d: %s\n",
config_error_line(&config), config_error_text(&config));
config_destroy(&config);
goto error;
}
/* Traverse the document */
config_setting_t *root = config_root_setting(&config);
if(janus_config_jfcg_parse(jc, NULL, root) < 0) {
JANUS_LOG(LOG_ERR, "Error parsing config file at line %d: %s\n",
config_error_line(&config), config_error_text(&config));
config_destroy(&config);
goto error;
}
config_destroy(&config);
/* We're done */
goto done;
}
/* Not libconfig: assume INI, traverse manually and parse it */
int line_number = 0;
char line_buffer[BUFSIZ];
janus_config_category *cg = NULL;
while(fgets(line_buffer, sizeof(line_buffer), file)) {
line_number++;
if(strlen(line_buffer) == 0)
continue;
/* Strip comments */
char *line = line_buffer, *sc = line, *c = NULL;
while((c = strchr(sc, ';')) != NULL) {
if(c == line || *(c-1) != '\\') {
/* Comment starts here */
*c = '\0';
break;
}
/* Escaped semicolon, remove the slash */
sc = c-1;
/* length will be at least 2: ';' '\0' */
memmove(sc, c, strlen(c)+1);
/* Go on */
sc++;
}
/* Trim (will remove newline characters too) */
line = trim(line);
if(strlen(line) == 0)
continue;
/* Parse */
if(line[0] == '[') {
/* Category */
line++;
char *end = strchr(line, ']');
if(end == NULL) {
JANUS_LOG(LOG_ERR, "Error parsing category at line %d: syntax error (%s)\n", line_number, filename);
goto error;
}
*end = '\0';
line = trim(line);
if(strlen(line) == 0) {
JANUS_LOG(LOG_ERR, "Error parsing category at line %d: no name (%s)\n", line_number, filename);
goto error;
}
cg = janus_config_category_create(line);
if(janus_config_add(jc, NULL, cg) < 0) {
janus_config_container_destroy(cg);
JANUS_LOG(LOG_ERR, "Error adding category %s (%s)\n", line, filename);
goto error;
}
} else {
/* Item */
char *name = line, *value = strchr(line, '=');
if(value == NULL || value == line) {
JANUS_LOG(LOG_ERR, "Error parsing item at line %d (%s)\n", line_number, filename);
goto error;
}
*value = '\0';
name = trim(name);
if(strlen(name) == 0) {
JANUS_LOG(LOG_ERR, "Error parsing item at line %d: no name (%s)\n", line_number, filename);
goto error;
}
value++;
value = trim(value);
if(strlen(value) > 0) {
if(*value == '>') {
value++;
value = trim(value);
if(strlen(value) == 0) {
JANUS_LOG(LOG_ERR, "Error parsing item at line %d: no value (%s)\n", line_number, filename);
goto error;
}
}
}
janus_config_item *item = janus_config_item_create(name, value);
if(janus_config_add(jc, cg, item) < 0) {
janus_config_container_destroy(item);
if(cg == NULL)
JANUS_LOG(LOG_ERR, "Error adding item %s (%s)\n", name, filename);
else
JANUS_LOG(LOG_ERR, "Error adding item %s to category %s (%s)\n", name, cg->name, filename);
goto error;
}
}
}
done:
g_free(tmp_filename);
fclose(file);
return jc;
error:
g_free(tmp_filename);
fclose(file);
janus_config_destroy(jc);
return NULL;
}
janus_config *janus_config_create(const char *name) {
if(name == NULL)
return NULL;
janus_config *jc = g_malloc0(sizeof(janus_config));
jc->name = g_strdup(name);
/* Is this a libconfig or INI config file? */
jc->is_jcfg = is_jcfg_config(jc->name);
return jc;
}
/* Containers management */
janus_config_item *janus_config_item_create(const char *name, const char *value) {
if(name == NULL && value == NULL)
return NULL;
janus_config_item *item = g_malloc0(sizeof(janus_config_item));
item->type = janus_config_type_item;
if(name)
item->name = g_strdup(name);
if(value)
item->value = g_strdup(value);
return item;
}
janus_config_category *janus_config_category_create(const char *name) {
janus_config_category *category = g_malloc0(sizeof(janus_config_category));
category->type = janus_config_type_category;
if(name != NULL)
category->name = g_strdup(name);
return category;
}
janus_config_array *janus_config_array_create(const char *name) {
janus_config_array *array = g_malloc0(sizeof(janus_config_array));
array->type = janus_config_type_array;
if(name != NULL)
array->name = g_strdup(name);
return array;
}
void janus_config_container_destroy(janus_config_container *container) {
if(container) {
if(container->name)
g_free((gpointer)container->name);
if(container->value)
g_free((gpointer)container->value);
if(container->list)
g_list_free_full(container->list, (GDestroyNotify)janus_config_container_destroy);
g_free(container);
}
}
static janus_config_container *janus_config_get_internal(janus_config *config,
janus_config_container *parent, janus_config_type type, const char *name, gboolean create) {
if(config == NULL || name == NULL)
return NULL;
if(parent != NULL && parent->type != janus_config_type_category && parent->type != janus_config_type_array)
return NULL;
janus_config_container *c = NULL;
GList *l = parent ? parent->list : config->list;
while(l) {
c = (janus_config_container *)l->data;
if(c && c->name && !strcasecmp(name, c->name) &&
(type == janus_config_type_any || c->type == type))
return c;
l = l->next;
}
/* If we got here, it doesn't exist, should we create it? */
c = NULL;
if(create) {
if(type == janus_config_type_category) {
c = janus_config_category_create(name);
} else if(type == janus_config_type_array) {
c = janus_config_array_create(name);
} else {
JANUS_LOG(LOG_WARN, "Not a category and not an array, not creating anything...\n");
}
if(c != NULL && janus_config_add(config, parent, c) < 0) {
janus_config_container_destroy(c);
JANUS_LOG(LOG_ERR, "Error adding item %s to %s\n", name, parent ? parent->name : "root");
return NULL;
}
}
return c;
}
janus_config_container *janus_config_get(janus_config *config,
janus_config_container *parent, janus_config_type type, const char *name) {
return janus_config_get_internal(config, parent, type, name, FALSE);
}
janus_config_container *janus_config_get_create(janus_config *config,
janus_config_container *parent, janus_config_type type, const char *name) {
return janus_config_get_internal(config, parent, type, name, TRUE);
}
janus_config_container *janus_config_search(janus_config *config, ...) {
if(config == NULL)
return NULL;
va_list args;
va_start(args, config);
char *name = va_arg(args, char *);
if(name == NULL) {
va_end(args);
return NULL;
}
/* Get the full path we're looking for */
GList *path = NULL;
while(name) {
path = g_list_append(path, name);
name = va_arg(args, char *);
}
va_end(args);
/* Start looking */
janus_config_container *parent = NULL, *c = NULL;
while(path) {
name = (char *)path->data;
c = janus_config_get(config, parent, janus_config_type_any, name);
if(c == NULL) {
/* Not found */
break;
}
parent = c;
/* Next parent */
path = path->next;
}
return c;
}
int janus_config_add(janus_config *config, janus_config_container *container, janus_config_container *item) {
if(config == NULL || item == NULL)
return -1;
if(container != NULL && container->type != janus_config_type_category && container->type != janus_config_type_array)
return -2;
if(container) {
/* Add to parent */
container->list = g_list_append(container->list, item);
} else {
/* Add to root */
config->list = g_list_append(config->list, item);
}
return 0;
}
int janus_config_remove(janus_config *config, janus_config_container *container, const char *name) {
if(config == NULL || name == NULL)
return -1;
if(container != NULL && container->type != janus_config_type_category && container->type != janus_config_type_array)
return -2;
janus_config_container *item = janus_config_get(config, container, janus_config_type_any, name);
if(item == NULL)
return -3;
if(container) {
/* Remove from parent */
container->list = g_list_remove(container->list, item);
} else {
/* Remove from root */
config->list = g_list_remove(config->list, item);
}
janus_config_container_destroy(item);
return 0;
}
GList *janus_config_get_items(janus_config *config, janus_config_container *parent) {
if(config == NULL || (parent != NULL && parent->type != janus_config_type_category
&& parent->type != janus_config_type_array))
return NULL;
GList *list = NULL, *clist = parent ? parent->list : config->list;
while(clist) {
janus_config_container *c = (janus_config_container *)clist->data;
if(c && c->type == janus_config_type_item)
list = g_list_append(list, c);
clist = clist->next;
}
return list;
}
GList *janus_config_get_categories(janus_config *config, janus_config_container *parent) {
if(config == NULL || (parent != NULL && parent->type != janus_config_type_category
&& parent->type != janus_config_type_array))
return NULL;
GList *list = NULL, *clist = parent ? parent->list : config->list;
while(clist) {
janus_config_container *c = (janus_config_container *)clist->data;
if(c && c->type == janus_config_type_category)
list = g_list_append(list, c);
clist = clist->next;
}
return list;
}
GList *janus_config_get_arrays(janus_config *config, janus_config_container *parent) {
if(config == NULL || (parent != NULL && parent->type != janus_config_type_category
&& parent->type != janus_config_type_array))
return NULL;
GList *list = NULL, *clist = parent ? parent->list : config->list;
while(clist) {
janus_config_container *c = (janus_config_container *)clist->data;
if(c && c->type == janus_config_type_array)
list = g_list_append(list, c);
clist = clist->next;
}
return list;
}
/* Printing utilities */
void janus_config_print(janus_config *config) {
janus_config_print_as(config, LOG_VERB);
}
#define JANUS_CONFIG_INDENT 4
static void janus_config_print_list(int level, GList *l, int indent) {
while(l) {
janus_config_container *c = (janus_config_container *)l->data;
if(c->type == janus_config_type_item) {
JANUS_LOG(level, "%*s%s%s%s\n", indent, "",
c->name ? c->name : "",
c->name && c->value ? ": " : "",
c->value ? c->value : "");
} else if(c->type == janus_config_type_category) {
JANUS_LOG(level, "%*s%s%s{\n", indent, "",
c->name ? c->name : "",
c->name ? ": " : "");
if(c->list)
janus_config_print_list(level, c->list, indent+JANUS_CONFIG_INDENT);
JANUS_LOG(level, "%*s}\n", indent, "");
} else if(c->type == janus_config_type_array) {
JANUS_LOG(level, "%*s%s%s[\n", indent, "",
c->name ? c->name : "",
c->name ? ": " : "");
if(c->list)
janus_config_print_list(level, c->list, indent+JANUS_CONFIG_INDENT);
JANUS_LOG(level, "%*s]\n", indent, "");
}
l = l->next;
}
}
void janus_config_print_as(janus_config *config, int level) {
if(config == NULL)
return;
JANUS_LOG(level, "[%s]\n", config->name ? config->name : "??");
if(config->list)
janus_config_print_list(level, config->list, JANUS_CONFIG_INDENT);
}
static void janus_config_save_list(janus_config *config, FILE *file, int level, gboolean array, GList *list, config_setting_t *lcfg) {
if(config == NULL || file == NULL || list == NULL )
return;
GList *l = list;
config_setting_t *elem = NULL;
while(l) {
janus_config_container *c = (janus_config_container *)l->data;
if(c->type == janus_config_type_item) {
if(config->is_jcfg) {
elem = config_setting_add(lcfg, c->name, CONFIG_TYPE_STRING);
if(elem == NULL) {
JANUS_LOG(LOG_ERR, "Error saving string '%s' to the config file...\n", c->name);
l = l->next;
continue;
}
config_setting_set_string(elem, c->value);
} else {
fwrite(c->name, sizeof(char), strlen(c->name), file);
fwrite(" = ", sizeof(char), 3, file);
/* If the value contains a semicolon, escape it */
if(strchr(c->value, ';')) {
char *value = g_strdup(c->value);
value = janus_string_replace((char *)value, ";", "\\;");
fwrite(value, sizeof(char), strlen(value), file);
fwrite("\n", sizeof(char), 1, file);
g_free(value);
} else {
/* No need to escape */
fwrite(c->value, sizeof(char), strlen(c->value), file);
fwrite("\n", sizeof(char), 1, file);
}
}
} else if(c->type == janus_config_type_category) {
if(config->is_jcfg) {
elem = config_setting_add(lcfg, c->name, CONFIG_TYPE_GROUP);
if(elem == NULL) {
JANUS_LOG(LOG_ERR, "Error saving group '%s' to the config file...\n", c->name);
l = l->next;
continue;
}
} else {
if(level > 0) {
/* INI files don't support indented categories */
JANUS_LOG(LOG_WARN, "Dropping indented category %s (unsupported in INI files)\n", c->name);
} else {
fwrite("[", sizeof(char), 1, file);
fwrite(c->name, sizeof(char), strlen(c->name), file);
fwrite("]\n", sizeof(char), 2, file);
}
}
if(c->list != NULL) {
/* Non-empty category */
janus_config_save_list(config, file, level+1, FALSE, c->list, elem);
}
if(!config->is_jcfg)
fwrite("\r\n", sizeof(char), 2, file);
} else if(c->type == janus_config_type_array) {
if(!config->is_jcfg) {
/* INI files don't support arrays */
JANUS_LOG(LOG_WARN, "Dropping array %s (unsupported in INI files)\n", c->name);
} else {
/* FIXME We don't know in advance if all items will be of the
* same kind, so we use list instead of array in libconfig */
elem = config_setting_add(lcfg, c->name, CONFIG_TYPE_LIST);
if(elem == NULL) {
JANUS_LOG(LOG_ERR, "Error saving list '%s' to the config file...\n", c->name);
l = l->next;
continue;
}
if(c->list != NULL) {
/* Non-empty array */
janus_config_save_list(config, file, level+1, FALSE, c->list, elem);
}
}
}
l = l->next;
}
}
gboolean janus_config_save(janus_config *config, const char *folder, const char *filename) {
if(config == NULL)
return -1;
/* If this is a libconfig configuration, create an object for it */
config_t lcfg;
if(config->is_jcfg)
config_init(&lcfg);
/* Open the file */
FILE *file = NULL;
char path[1024];
if(folder != NULL) {
/* Create folder, if needed */
if(janus_mkdir(folder, 0755) < 0) {
JANUS_LOG(LOG_ERR, "Couldn't save configuration file, error creating folder '%s'...\n", folder);
if(config->is_jcfg)
config_destroy(&lcfg);
return -2;
}
g_snprintf(path, 1024, "%s/%s.%s", folder, filename, config->is_jcfg ? "jcfg" : "cfg");
} else {
g_snprintf(path, 1024, "%s.%s", filename, config->is_jcfg ? "jcfg" : "cfg");
}
file = fopen(path, "wt");
if(file == NULL) {
JANUS_LOG(LOG_ERR, "Couldn't save configuration file, error opening file '%s'...\n", path);
if(config->is_jcfg)
config_destroy(&lcfg);
return -3;
}
/* Print a header/comment */
char date[64], header[256];
struct tm tmresult;
time_t ltime = time(NULL);
localtime_r(<ime, &tmresult);
strftime(date, sizeof(date), "%a %b %e %T %Y", &tmresult);
char comment = config->is_jcfg ? '#' : ';';
g_snprintf(header, 256, "%c\n%c File automatically generated on %s\n%c\n\n",
comment, comment, date, comment);
fwrite(header, sizeof(char), strlen(header), file);
/* Go on with the configuration */
if(config->list)
janus_config_save_list(config, file, 0, FALSE, config->list, config->is_jcfg ? config_root_setting(&lcfg) : NULL);
/* If this is a libconfig output file, save and destroy the object */
if(config->is_jcfg) {
config_write(&lcfg, file);
config_destroy(&lcfg);
}
/* Done */
fclose(file);
return 0;
}
void janus_config_destroy(janus_config *config) {
if(config == NULL)
return;
if(config->list) {
g_list_free_full(config->list, (GDestroyNotify)janus_config_container_destroy);
config->list = NULL;
}
g_free((gpointer)config->name);
g_free((gpointer)config);
config = NULL;
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。