Merge branch 'settings-parser'
Adds a flex/bison based parser for settings_t. It provides several improvements over the previous parser e.g. quoted strings (with escape sequences), unlimited includes, more relaxed newline handling, better syntax error reporting, and a distinction between empty and unset values (key = vs. key = "").
This commit is contained in:
+1
-1
@@ -55,7 +55,7 @@ cov-report:
|
||||
lcov -r $(top_builddir)/coverage/coverage.info '*/tests/*' \
|
||||
-o $(top_builddir)/coverage/coverage.cleaned.info \
|
||||
--rc lcov_branch_coverage=1
|
||||
genhtml --num-spaces 4 --legend --branch-coverage \
|
||||
genhtml --num-spaces 4 --legend --branch-coverage --ignore-errors source \
|
||||
-t "$(PACKAGE_STRING)" \
|
||||
-o $(top_builddir)/coverage/html \
|
||||
-p `readlink -m $(abs_top_srcdir)`/src \
|
||||
|
||||
+2
-1
@@ -5,7 +5,7 @@ AM_CPPFLAGS = \
|
||||
|
||||
noinst_PROGRAMS = bin2array bin2sql id2sql key2keyid keyid2sql oid2der \
|
||||
thread_analysis dh_speed pubkey_speed crypt_burn hash_burn fetch \
|
||||
dnssec malloc_speed aes-test
|
||||
dnssec malloc_speed aes-test settings-test
|
||||
|
||||
if USE_TLS
|
||||
noinst_PROGRAMS += tls_test
|
||||
@@ -40,6 +40,7 @@ malloc_speed_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la $(RTLIB)
|
||||
fetch_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la
|
||||
dnssec_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la
|
||||
aes_test_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la
|
||||
settings_test_LDADD = $(top_builddir)/src/libstrongswan/libstrongswan.la
|
||||
|
||||
key2keyid.o : $(top_builddir)/config.status
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <library.h>
|
||||
#include <settings/settings_types.h>
|
||||
|
||||
/**
|
||||
* Defined in libstrongswan but not part of the public API
|
||||
*/
|
||||
bool settings_parser_parse_file(void *this, char *name);
|
||||
|
||||
/**
|
||||
* Recursively print the section and all subsections/settings
|
||||
*/
|
||||
static void print_section(section_t *section, int level)
|
||||
{
|
||||
section_t *sub;
|
||||
kv_t *kv;
|
||||
int i;
|
||||
char indent[256];
|
||||
|
||||
for (i = 0; i < level * 2 && i < sizeof(indent) - 2; i += 2)
|
||||
{
|
||||
indent[i ] = ' ';
|
||||
indent[i+1] = ' ';
|
||||
}
|
||||
indent[i] = '\0';
|
||||
|
||||
for (i = 0; i < array_count(section->kv_order); i++)
|
||||
{
|
||||
array_get(section->kv_order, i, &kv);
|
||||
printf("%s%s = %s\n", indent, kv->key, kv->value);
|
||||
}
|
||||
for (i = 0; i < array_count(section->sections_order); i++)
|
||||
{
|
||||
array_get(section->sections_order, i, &sub);
|
||||
printf("%s%s {\n", indent, sub->name);
|
||||
print_section(sub, level + 1);
|
||||
printf("%s}\n", indent);
|
||||
}
|
||||
}
|
||||
|
||||
static void usage(FILE *out, char *name)
|
||||
{
|
||||
fprintf(out, "Test strongswan.conf parser\n\n");
|
||||
fprintf(out, "%s [OPTIONS]\n\n", name);
|
||||
fprintf(out, "Options:\n");
|
||||
fprintf(out, " -h, --help print this help.\n");
|
||||
fprintf(out, " -d, --debug enables debugging of the parser.\n");
|
||||
fprintf(out, " -f, --file=FILE config file to load (default STDIN).\n");
|
||||
fprintf(out, "\n");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char *file = NULL;
|
||||
|
||||
/* don't load strongswan.conf */
|
||||
library_init("", "settings-test");
|
||||
atexit(library_deinit);
|
||||
|
||||
dbg_default_set_level(3);
|
||||
|
||||
while (true)
|
||||
{
|
||||
struct option long_opts[] = {
|
||||
{"help", no_argument, NULL, 'h' },
|
||||
{"debug", no_argument, NULL, 'd' },
|
||||
{"file", required_argument, NULL, 'f' },
|
||||
{0,0,0,0 },
|
||||
};
|
||||
switch (getopt_long(argc, argv, "hdf:", long_opts, NULL))
|
||||
{
|
||||
case EOF:
|
||||
break;
|
||||
case 'h':
|
||||
usage(stdout, argv[0]);
|
||||
return 0;
|
||||
case 'd':
|
||||
setenv("DEBUG_SETTINGS_PARSER", "1", TRUE);
|
||||
continue;
|
||||
case 'f':
|
||||
file = optarg;
|
||||
continue;
|
||||
default:
|
||||
usage(stderr, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (file)
|
||||
{
|
||||
section_t *root = settings_section_create(strdup("root"));
|
||||
|
||||
settings_parser_parse_file(root, file);
|
||||
|
||||
print_section(root, 0);
|
||||
|
||||
settings_section_destroy(root, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
usage(stderr, argv[0]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -126,12 +126,15 @@ static int run()
|
||||
{
|
||||
DBG1(DBG_DMN, "signal of type SIGHUP received. Reloading "
|
||||
"configuration");
|
||||
if (lib->settings->load_files(lib->settings, NULL, FALSE))
|
||||
#ifdef STRONGSWAN_CONF
|
||||
if (lib->settings->load_files(lib->settings, STRONGSWAN_CONF,
|
||||
FALSE))
|
||||
{
|
||||
charon->load_loggers(charon, levels, TRUE);
|
||||
lib->plugins->reload(lib->plugins, NULL);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
DBG1(DBG_DMN, "reloading config failed, keeping old");
|
||||
}
|
||||
|
||||
+4
-1
@@ -122,12 +122,15 @@ static void run()
|
||||
{
|
||||
DBG1(DBG_DMN, "signal of type SIGHUP received. Reloading "
|
||||
"configuration");
|
||||
if (lib->settings->load_files(lib->settings, NULL, FALSE))
|
||||
#ifdef STRONGSWAN_CONF
|
||||
if (lib->settings->load_files(lib->settings, STRONGSWAN_CONF,
|
||||
FALSE))
|
||||
{
|
||||
charon->load_loggers(charon, levels, !use_syslog);
|
||||
lib->plugins->reload(lib->plugins, NULL);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
DBG1(DBG_DMN, "reloading config failed, keeping old");
|
||||
}
|
||||
|
||||
@@ -32,12 +32,14 @@ networking/streams/stream_service.c networking/streams/stream_manager.c \
|
||||
pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c \
|
||||
processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \
|
||||
processing/watcher.c resolver/resolver_manager.c resolver/rr_set.c \
|
||||
selectors/traffic_selector.c threading/thread.c threading/thread_value.c \
|
||||
threading/mutex.c threading/semaphore.c threading/rwlock.c threading/spinlock.c \
|
||||
selectors/traffic_selector.c settings/settings.c settings/settings_types.c \
|
||||
settings/settings_parser.c settings/settings_lexer.c \
|
||||
threading/thread.c threading/thread_value.c threading/mutex.c \
|
||||
threading/semaphore.c threading/rwlock.c threading/spinlock.c \
|
||||
utils/utils.c utils/chunk.c utils/debug.c utils/enum.c utils/identification.c \
|
||||
utils/lexparser.c utils/optionsfrom.c utils/capabilities.c utils/backtrace.c \
|
||||
utils/printf_hook/printf_hook_builtin.c utils/settings.c utils/test.c \
|
||||
utils/utils/strerror.c
|
||||
utils/parser_helper.c utils/test.c utils/utils/strerror.c \
|
||||
utils/printf_hook/printf_hook_builtin.c
|
||||
|
||||
# adding the plugin source files
|
||||
|
||||
|
||||
@@ -30,12 +30,17 @@ networking/streams/stream_service.c networking/streams/stream_manager.c \
|
||||
pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c \
|
||||
processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \
|
||||
processing/watcher.c resolver/resolver_manager.c resolver/rr_set.c \
|
||||
selectors/traffic_selector.c threading/thread.c threading/thread_value.c \
|
||||
threading/mutex.c threading/semaphore.c threading/rwlock.c threading/spinlock.c \
|
||||
selectors/traffic_selector.c settings/settings.c settings/settings_types.c \
|
||||
settings/settings_parser.y settings/settings_lexer.l \
|
||||
threading/thread.c threading/thread_value.c threading/mutex.c \
|
||||
threading/semaphore.c threading/rwlock.c threading/spinlock.c \
|
||||
utils/utils.c utils/chunk.c utils/debug.c utils/enum.c utils/identification.c \
|
||||
utils/lexparser.c utils/optionsfrom.c utils/capabilities.c utils/backtrace.c \
|
||||
utils/settings.c utils/test.c \
|
||||
utils/utils/strerror.c
|
||||
utils/parser_helper.c utils/test.c utils/utils/strerror.c
|
||||
|
||||
# private header files
|
||||
noinst_HEADERS = \
|
||||
settings/settings_types.h
|
||||
|
||||
if USE_DEV_HEADERS
|
||||
strongswan_includedir = ${dev_headers}
|
||||
@@ -75,14 +80,14 @@ resolver/rr.h resolver/resolver_manager.h \
|
||||
plugins/plugin_loader.h plugins/plugin.h plugins/plugin_feature.h \
|
||||
processing/jobs/job.h processing/jobs/callback_job.h processing/processor.h \
|
||||
processing/scheduler.h processing/watcher.h selectors/traffic_selector.h \
|
||||
threading/thread.h threading/thread_value.h \
|
||||
settings/settings.h threading/thread.h threading/thread_value.h \
|
||||
threading/mutex.h threading/condvar.h threading/spinlock.h threading/semaphore.h \
|
||||
threading/rwlock.h threading/rwlock_condvar.h threading/lock_profiler.h \
|
||||
utils/utils.h utils/chunk.h utils/debug.h utils/enum.h utils/identification.h \
|
||||
utils/lexparser.h utils/optionsfrom.h utils/capabilities.h utils/backtrace.h \
|
||||
utils/leak_detective.h utils/printf_hook/printf_hook.h \
|
||||
utils/printf_hook/printf_hook_vstr.h utils/printf_hook/printf_hook_builtin.h \
|
||||
utils/settings.h utils/test.h utils/integrity_checker.h \
|
||||
utils/parser_helper.h utils/test.h utils/integrity_checker.h \
|
||||
utils/utils/strerror.h
|
||||
endif
|
||||
|
||||
@@ -103,6 +108,8 @@ AM_CFLAGS = \
|
||||
AM_LDFLAGS = \
|
||||
-no-undefined
|
||||
|
||||
AM_YFLAGS = -v -d
|
||||
|
||||
if USE_LEAK_DETECTIVE
|
||||
AM_CPPFLAGS += -DLEAK_DETECTIVE
|
||||
libstrongswan_la_SOURCES += utils/leak_detective.c
|
||||
@@ -144,7 +151,8 @@ Android.mk AndroidConfigLocal.h
|
||||
|
||||
BUILT_SOURCES = \
|
||||
$(srcdir)/asn1/oid.c $(srcdir)/asn1/oid.h \
|
||||
$(srcdir)/crypto/proposal/proposal_keywords_static.c
|
||||
$(srcdir)/crypto/proposal/proposal_keywords_static.c \
|
||||
settings/settings_parser.h
|
||||
|
||||
MAINTAINERCLEANFILES = \
|
||||
$(srcdir)/asn1/oid.c $(srcdir)/asn1/oid.h \
|
||||
|
||||
@@ -168,7 +168,7 @@ array_t *array_create(u_int esize, u_int8_t reserve)
|
||||
);
|
||||
if (array->tail)
|
||||
{
|
||||
array->data = malloc(array->tail * array->esize);
|
||||
array->data = malloc(get_size(array, array->tail));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2008 Tobias Brunner
|
||||
* Copyright (C) 2008-2013 Tobias Brunner
|
||||
* Copyright (C) 2007 Martin Willi
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
@@ -25,6 +25,10 @@
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef HAVE_GLOB_H
|
||||
#include <glob.h>
|
||||
#endif /* HAVE_GLOB_H */
|
||||
|
||||
#include <utils/debug.h>
|
||||
|
||||
/**
|
||||
@@ -157,8 +161,106 @@ enumerator_t* enumerator_create_directory(const char *path)
|
||||
return &this->public;
|
||||
}
|
||||
|
||||
#ifdef HAVE_GLOB_H
|
||||
|
||||
/**
|
||||
* Enumerator implementation for directory enumerator
|
||||
* Enumerator implementation for glob enumerator
|
||||
*/
|
||||
typedef struct {
|
||||
/** implements enumerator_t */
|
||||
enumerator_t public;
|
||||
/** glob data */
|
||||
glob_t glob;
|
||||
/** current match */
|
||||
char **match;
|
||||
/** absolute path of current file */
|
||||
char full[PATH_MAX];
|
||||
} glob_enum_t;
|
||||
|
||||
/**
|
||||
* Implementation of enumerator_create_glob().destroy
|
||||
*/
|
||||
static void destroy_glob_enum(glob_enum_t *this)
|
||||
{
|
||||
globfree(&this->glob);
|
||||
free(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of enumerator_create_glob().enumerate
|
||||
*/
|
||||
static bool enumerate_glob_enum(glob_enum_t *this, char **file, struct stat *st)
|
||||
{
|
||||
char *match = *(++this->match);
|
||||
|
||||
if (!match)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
if (file)
|
||||
{
|
||||
*file = match;
|
||||
}
|
||||
if (st)
|
||||
{
|
||||
if (stat(match, st))
|
||||
{
|
||||
DBG1(DBG_LIB, "stat() on '%s' failed: %s", match,
|
||||
strerror(errno));
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* See header
|
||||
*/
|
||||
enumerator_t* enumerator_create_glob(const char *pattern)
|
||||
{
|
||||
glob_enum_t *this;
|
||||
int status;
|
||||
|
||||
if (!pattern)
|
||||
{
|
||||
return enumerator_create_empty();
|
||||
}
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.enumerate = (void*)enumerate_glob_enum,
|
||||
.destroy = (void*)destroy_glob_enum,
|
||||
},
|
||||
.glob = {
|
||||
.gl_offs = 1, /* reserve one slot so we can enumerate easily */
|
||||
}
|
||||
);
|
||||
|
||||
status = glob(pattern, GLOB_DOOFFS | GLOB_ERR, NULL, &this->glob);
|
||||
if (status == GLOB_NOMATCH)
|
||||
{
|
||||
DBG1(DBG_LIB, "no files found matching '%s'", pattern);
|
||||
}
|
||||
else if (status != 0)
|
||||
{
|
||||
DBG1(DBG_LIB, "expanding file pattern '%s' failed: %s", pattern,
|
||||
strerror(errno));
|
||||
}
|
||||
this->match = this->glob.gl_pathv;
|
||||
return &this->public;
|
||||
}
|
||||
|
||||
#else /* HAVE_GLOB_H */
|
||||
|
||||
enumerator_t* enumerator_create_glob(const char *pattern)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* HAVE_GLOB_H */
|
||||
|
||||
/**
|
||||
* Enumerator implementation for token enumerator
|
||||
*/
|
||||
typedef struct {
|
||||
/** implements enumerator_t */
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Tobias Brunner
|
||||
* Copyright (C) 2007 Martin Willi
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
@@ -69,7 +70,9 @@ enumerator_t *enumerator_create_single(void *item, void (*cleanup)(void *item));
|
||||
* This enumerator_t.enumerate() function returns a (to the directory) relative
|
||||
* filename (as a char*), an absolute filename (as a char*) and a file status
|
||||
* (to a struct stat), which all may be NULL. "." and ".." entries are
|
||||
* skipped. Example:
|
||||
* skipped.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
char *rel, *abs;
|
||||
@@ -95,6 +98,38 @@ enumerator_t *enumerator_create_single(void *item, void (*cleanup)(void *item));
|
||||
*/
|
||||
enumerator_t* enumerator_create_directory(const char *path);
|
||||
|
||||
/**
|
||||
* Create an enumerator over files/directories matching a file pattern.
|
||||
*
|
||||
* This enumerator_t.enumerate() function returns the filename (as a char*),
|
||||
* and a file status (to a struct stat), which both may be NULL.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
char *file;
|
||||
struct stat st;
|
||||
enumerator_t *e;
|
||||
|
||||
e = enumerator_create_glob("/etc/ipsec.*.conf");
|
||||
if (e)
|
||||
{
|
||||
while (e->enumerate(e, &file, &st))
|
||||
{
|
||||
if (S_ISREG(st.st_mode))
|
||||
{
|
||||
printf("%s\n", file);
|
||||
}
|
||||
}
|
||||
e->destroy(e);
|
||||
}
|
||||
@endcode
|
||||
*
|
||||
* @param pattern file pattern to match
|
||||
* @return the enumerator, NULL if not supported
|
||||
*/
|
||||
enumerator_t* enumerator_create_glob(const char *pattern);
|
||||
|
||||
/**
|
||||
* Create an enumerator over tokens of a string.
|
||||
*
|
||||
|
||||
@@ -298,6 +298,13 @@ bool library_init(char *settings, const char *namespace)
|
||||
|
||||
this->objects = hashtable_create((hashtable_hash_t)hash,
|
||||
(hashtable_equals_t)equals, 4);
|
||||
|
||||
#ifdef STRONGSWAN_CONF
|
||||
if (!settings)
|
||||
{
|
||||
settings = STRONGSWAN_CONF;
|
||||
}
|
||||
#endif
|
||||
this->public.settings = settings_create(settings);
|
||||
/* all namespace settings may fall back to libstrongswan */
|
||||
lib->settings->add_fallback(lib->settings, lib->ns, "libstrongswan");
|
||||
|
||||
@@ -113,8 +113,8 @@
|
||||
#include "utils/capabilities.h"
|
||||
#include "utils/integrity_checker.h"
|
||||
#include "utils/leak_detective.h"
|
||||
#include "utils/settings.h"
|
||||
#include "plugins/plugin_loader.h"
|
||||
#include "settings/settings.h"
|
||||
|
||||
typedef struct library_t library_t;
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
settings_lexer.c
|
||||
settings_parser.[ch]
|
||||
settings_parser.output
|
||||
@@ -24,11 +24,8 @@
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef HAVE_GLOB_H
|
||||
#include <glob.h>
|
||||
#endif /* HAVE_GLOB_H */
|
||||
|
||||
#include "settings.h"
|
||||
#include "settings_types.h"
|
||||
|
||||
#include "collections/array.h"
|
||||
#include "collections/hashtable.h"
|
||||
@@ -36,188 +33,42 @@
|
||||
#include "threading/rwlock.h"
|
||||
#include "utils/debug.h"
|
||||
|
||||
#define MAX_INCLUSION_LEVEL 10
|
||||
|
||||
typedef struct private_settings_t private_settings_t;
|
||||
typedef struct section_t section_t;
|
||||
typedef struct kv_t kv_t;
|
||||
|
||||
/**
|
||||
* private data of settings
|
||||
* Parse function provided by the generated parser.
|
||||
*/
|
||||
bool settings_parser_parse_file(section_t *root, char *name);
|
||||
|
||||
/**
|
||||
* Private data of settings
|
||||
*/
|
||||
struct private_settings_t {
|
||||
|
||||
/**
|
||||
* public functions
|
||||
* Public interface
|
||||
*/
|
||||
settings_t public;
|
||||
|
||||
/**
|
||||
* top level section
|
||||
* Top level section
|
||||
*/
|
||||
section_t *top;
|
||||
|
||||
/**
|
||||
* contents of loaded files and in-memory settings (char*)
|
||||
* Contents of replaced settings (char*)
|
||||
*
|
||||
* FIXME: This is required because the pointer returned by get_str()
|
||||
* is not refcounted. Might cause ever increasing usage stats.
|
||||
*/
|
||||
linked_list_t *contents;
|
||||
array_t *contents;
|
||||
|
||||
/**
|
||||
* lock to safely access the settings
|
||||
* Lock to safely access the settings
|
||||
*/
|
||||
rwlock_t *lock;
|
||||
};
|
||||
|
||||
/**
|
||||
* section containing subsections and key value pairs
|
||||
*/
|
||||
struct section_t {
|
||||
|
||||
/**
|
||||
* name of the section
|
||||
*/
|
||||
char *name;
|
||||
|
||||
/**
|
||||
* fallback sections, as section_t
|
||||
*/
|
||||
array_t *fallbacks;
|
||||
|
||||
/**
|
||||
* subsections, as section_t
|
||||
*/
|
||||
array_t *sections;
|
||||
|
||||
/**
|
||||
* key value pairs, as kv_t
|
||||
*/
|
||||
array_t *kv;
|
||||
};
|
||||
|
||||
/**
|
||||
* Key value pair
|
||||
*/
|
||||
struct kv_t {
|
||||
|
||||
/**
|
||||
* key string, relative
|
||||
*/
|
||||
char *key;
|
||||
|
||||
/**
|
||||
* value as string
|
||||
*/
|
||||
char *value;
|
||||
};
|
||||
|
||||
/**
|
||||
* create a key/value pair
|
||||
*/
|
||||
static kv_t *kv_create(char *key, char *value)
|
||||
{
|
||||
kv_t *this;
|
||||
INIT(this,
|
||||
.key = strdup(key),
|
||||
.value = value,
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* destroy a key/value pair
|
||||
*/
|
||||
static void kv_destroy(kv_t *this)
|
||||
{
|
||||
free(this->key);
|
||||
free(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* create a section with the given name
|
||||
*/
|
||||
static section_t *section_create(char *name)
|
||||
{
|
||||
section_t *this;
|
||||
INIT(this,
|
||||
.name = strdupnull(name),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* destroy a section
|
||||
*/
|
||||
static void section_destroy(section_t *this)
|
||||
{
|
||||
array_destroy_function(this->sections, (void*)section_destroy, NULL);
|
||||
array_destroy_function(this->kv, (void*)kv_destroy, NULL);
|
||||
array_destroy(this->fallbacks);
|
||||
free(this->name);
|
||||
free(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge contents of a section, returns if section can be safely removed.
|
||||
*/
|
||||
static bool section_purge(section_t *this)
|
||||
{
|
||||
section_t *current;
|
||||
int i;
|
||||
|
||||
array_destroy_function(this->kv, (void*)kv_destroy, NULL);
|
||||
this->kv = NULL;
|
||||
/* we ensure sections used as fallback, or configured with fallbacks (or
|
||||
* having any such subsections) are not removed */
|
||||
for (i = array_count(this->sections) - 1; i >= 0; i--)
|
||||
{
|
||||
array_get(this->sections, i, ¤t);
|
||||
if (section_purge(current))
|
||||
{
|
||||
array_remove(this->sections, i, NULL);
|
||||
section_destroy(current);
|
||||
}
|
||||
}
|
||||
return !this->fallbacks && !array_count(this->sections);
|
||||
}
|
||||
|
||||
/**
|
||||
* callback to find a section by name
|
||||
*/
|
||||
static int section_find(const void *a, const void *b)
|
||||
{
|
||||
const char *key = a;
|
||||
const section_t *item = b;
|
||||
return strcmp(key, item->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* callback to sort sections by name
|
||||
*/
|
||||
static int section_sort(const void *a, const void *b, void *user)
|
||||
{
|
||||
const section_t *sa = a, *sb = b;
|
||||
return strcmp(sa->name, sb->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* callback to find a kv pair by key
|
||||
*/
|
||||
static int kv_find(const void *a, const void *b)
|
||||
{
|
||||
const char *key = a;
|
||||
const kv_t *item = b;
|
||||
return strcmp(key, item->key);
|
||||
}
|
||||
|
||||
/**
|
||||
* callback to sort kv pairs by key
|
||||
*/
|
||||
static int kv_sort(const void *a, const void *b, void *user)
|
||||
{
|
||||
const kv_t *kva = a, *kvb = b;
|
||||
return strcmp(kva->key, kvb->key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a format key, but consume already processed arguments
|
||||
*/
|
||||
@@ -290,13 +141,13 @@ static section_t *find_section_buffered(section_t *section,
|
||||
{
|
||||
found = section;
|
||||
}
|
||||
else if (array_bsearch(section->sections, buf, section_find, &found) == -1)
|
||||
else if (array_bsearch(section->sections, buf, settings_section_find,
|
||||
&found) == -1)
|
||||
{
|
||||
if (ensure)
|
||||
{
|
||||
found = section_create(buf);
|
||||
array_insert_create(§ion->sections, ARRAY_TAIL, found);
|
||||
array_sort(section->sections, section_sort, NULL);
|
||||
found = settings_section_create(strdup(buf));
|
||||
settings_section_add(section, found, NULL);
|
||||
}
|
||||
}
|
||||
if (found && pos)
|
||||
@@ -340,7 +191,7 @@ static void find_sections_buffered(section_t *section, char *start, char *key,
|
||||
}
|
||||
else
|
||||
{
|
||||
array_bsearch(section->sections, buf, section_find, &found);
|
||||
array_bsearch(section->sections, buf, settings_section_find, &found);
|
||||
}
|
||||
if (found)
|
||||
{
|
||||
@@ -501,14 +352,13 @@ static kv_t *find_value_buffered(section_t *section, char *start, char *key,
|
||||
{
|
||||
found = section;
|
||||
}
|
||||
else if (array_bsearch(section->sections, buf, section_find,
|
||||
else if (array_bsearch(section->sections, buf, settings_section_find,
|
||||
&found) == -1)
|
||||
{
|
||||
if (ensure)
|
||||
{
|
||||
found = section_create(buf);
|
||||
array_insert_create(§ion->sections, ARRAY_TAIL, found);
|
||||
array_sort(section->sections, section_sort, NULL);
|
||||
found = settings_section_create(strdup(buf));
|
||||
settings_section_add(section, found, NULL);
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
@@ -532,13 +382,12 @@ static kv_t *find_value_buffered(section_t *section, char *start, char *key,
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
if (array_bsearch(section->kv, buf, kv_find, &kv) == -1)
|
||||
if (array_bsearch(section->kv, buf, settings_kv_find, &kv) == -1)
|
||||
{
|
||||
if (ensure)
|
||||
{
|
||||
kv = kv_create(buf, NULL);
|
||||
array_insert_create(§ion->kv, ARRAY_TAIL, kv);
|
||||
array_sort(section->kv, kv_sort, NULL);
|
||||
kv = settings_kv_create(strdup(buf), NULL);
|
||||
settings_kv_add(section, kv, NULL);
|
||||
}
|
||||
else if (section->fallbacks)
|
||||
{
|
||||
@@ -596,19 +445,7 @@ static void set_value(private_settings_t *this, section_t *section,
|
||||
TRUE);
|
||||
if (kv)
|
||||
{
|
||||
if (!value)
|
||||
{
|
||||
kv->value = NULL;
|
||||
}
|
||||
else if (kv->value && (strlen(value) <= strlen(kv->value)))
|
||||
{ /* overwrite in-place, if possible */
|
||||
strcpy(kv->value, value);
|
||||
}
|
||||
else
|
||||
{ /* otherwise clone the string and store it in the cache */
|
||||
kv->value = strdup(value);
|
||||
this->contents->insert_last(this->contents, kv->value);
|
||||
}
|
||||
settings_kv_set(kv, strdupnull(value), this->contents);
|
||||
}
|
||||
this->lock->unlock(this->lock);
|
||||
}
|
||||
@@ -892,7 +729,8 @@ static bool section_filter(hashtable_t *seen, section_t **in, char **out)
|
||||
static enumerator_t *section_enumerator(section_t *section,
|
||||
enumerator_data_t *data)
|
||||
{
|
||||
return enumerator_create_filter(array_create_enumerator(section->sections),
|
||||
return enumerator_create_filter(
|
||||
array_create_enumerator(section->sections_order),
|
||||
(void*)section_filter, data->seen, NULL);
|
||||
}
|
||||
|
||||
@@ -929,7 +767,7 @@ static bool kv_filter(hashtable_t *seen, kv_t **in, char **key,
|
||||
void *none, char **value)
|
||||
{
|
||||
*key = (*in)->key;
|
||||
if (seen->get(seen, *key))
|
||||
if (seen->get(seen, *key) || !(*in)->value)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
@@ -943,7 +781,7 @@ static bool kv_filter(hashtable_t *seen, kv_t **in, char **key,
|
||||
*/
|
||||
static enumerator_t *kv_enumerator(section_t *section, enumerator_data_t *data)
|
||||
{
|
||||
return enumerator_create_filter(array_create_enumerator(section->kv),
|
||||
return enumerator_create_filter(array_create_enumerator(section->kv_order),
|
||||
(void*)kv_filter, data->seen, NULL);
|
||||
}
|
||||
|
||||
@@ -989,464 +827,35 @@ METHOD(settings_t, add_fallback, void,
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* parse text, truncate "skip" chars, delimited by term respecting brackets.
|
||||
*
|
||||
* Chars in "skip" are truncated at the beginning and the end of the resulting
|
||||
* token. "term" contains a list of characters to read up to (first match),
|
||||
* while "br" contains bracket counterparts found in "term" to skip.
|
||||
*/
|
||||
static char parse(char **text, char *skip, char *term, char *br, char **token)
|
||||
{
|
||||
char *best = NULL;
|
||||
char best_term = '\0';
|
||||
|
||||
/* skip leading chars */
|
||||
while (strchr(skip, **text))
|
||||
{
|
||||
(*text)++;
|
||||
if (!**text)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/* mark begin of subtext */
|
||||
*token = *text;
|
||||
while (*term)
|
||||
{
|
||||
char *pos = *text;
|
||||
int level = 1;
|
||||
|
||||
/* find terminator */
|
||||
while (*pos)
|
||||
{
|
||||
if (*pos == *term)
|
||||
{
|
||||
level--;
|
||||
}
|
||||
else if (br && *pos == *br)
|
||||
{
|
||||
level++;
|
||||
}
|
||||
if (level == 0)
|
||||
{
|
||||
if (best == NULL || best > pos)
|
||||
{
|
||||
best = pos;
|
||||
best_term = *term;
|
||||
}
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
/* try next terminator */
|
||||
term++;
|
||||
if (br)
|
||||
{
|
||||
br++;
|
||||
}
|
||||
}
|
||||
if (best)
|
||||
{
|
||||
/* update input */
|
||||
*text = best;
|
||||
/* null trailing bytes */
|
||||
do
|
||||
{
|
||||
*best = '\0';
|
||||
best--;
|
||||
}
|
||||
while (best >= *token && strchr(skip, *best));
|
||||
/* return found terminator */
|
||||
return best_term;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if "text" starts with "pattern".
|
||||
* Characters in "skip" are skipped first. If found, TRUE is returned and "text"
|
||||
* is modified to point to the character right after "pattern".
|
||||
*/
|
||||
static bool starts_with(char **text, char *skip, char *pattern)
|
||||
{
|
||||
char *pos = *text;
|
||||
int len = strlen(pattern);
|
||||
while (strchr(skip, *pos))
|
||||
{
|
||||
pos++;
|
||||
if (!*pos)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
if (strlen(pos) < len || !strneq(pos, pattern, len))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
*text = pos + len;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if what follows in "text" is an include statement.
|
||||
* If this function returns TRUE, "text" will point to the character right after
|
||||
* the include pattern, which is returned in "pattern".
|
||||
*/
|
||||
static bool parse_include(char **text, char **pattern)
|
||||
{
|
||||
char *pos = *text;
|
||||
if (!starts_with(&pos, "\n\t ", "include"))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
if (starts_with(&pos, "\t ", "="))
|
||||
{ /* ignore "include = value" */
|
||||
return FALSE;
|
||||
}
|
||||
*text = pos;
|
||||
return parse(text, "\t ", "\n", NULL, pattern) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward declaration.
|
||||
*/
|
||||
static bool parse_files(linked_list_t *contents, char *file, int level,
|
||||
char *pattern, section_t *section);
|
||||
|
||||
/**
|
||||
* Parse a section
|
||||
*/
|
||||
static bool parse_section(linked_list_t *contents, char *file, int level,
|
||||
char **text, section_t *section)
|
||||
{
|
||||
bool finished = FALSE;
|
||||
char *key, *value, *inner;
|
||||
|
||||
while (!finished)
|
||||
{
|
||||
if (parse_include(text, &value))
|
||||
{
|
||||
if (!parse_files(contents, file, level, value, section))
|
||||
{
|
||||
DBG1(DBG_LIB, "failed to include '%s'", value);
|
||||
return FALSE;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
switch (parse(text, "\t\n ", "{=#", NULL, &key))
|
||||
{
|
||||
case '{':
|
||||
if (parse(text, "\t ", "}", "{", &inner))
|
||||
{
|
||||
section_t *sub;
|
||||
if (!strlen(key))
|
||||
{
|
||||
DBG1(DBG_LIB, "skipping section without name in '%s'",
|
||||
section->name);
|
||||
continue;
|
||||
}
|
||||
if (array_bsearch(section->sections, key, section_find,
|
||||
&sub) == -1)
|
||||
{
|
||||
sub = section_create(key);
|
||||
if (parse_section(contents, file, level, &inner, sub))
|
||||
{
|
||||
array_insert_create(§ion->sections, ARRAY_TAIL,
|
||||
sub);
|
||||
array_sort(section->sections, section_sort, NULL);
|
||||
continue;
|
||||
}
|
||||
section_destroy(sub);
|
||||
}
|
||||
else
|
||||
{ /* extend the existing section */
|
||||
if (parse_section(contents, file, level, &inner, sub))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
DBG1(DBG_LIB, "parsing subsection '%s' failed", key);
|
||||
break;
|
||||
}
|
||||
DBG1(DBG_LIB, "matching '}' not found near %s", *text);
|
||||
break;
|
||||
case '=':
|
||||
if (parse(text, "\t ", "\n", NULL, &value))
|
||||
{
|
||||
kv_t *kv;
|
||||
if (!strlen(key))
|
||||
{
|
||||
DBG1(DBG_LIB, "skipping value without key in '%s'",
|
||||
section->name);
|
||||
continue;
|
||||
}
|
||||
if (array_bsearch(section->kv, key, kv_find, &kv) == -1)
|
||||
{
|
||||
kv = kv_create(key, value);
|
||||
array_insert_create(§ion->kv, ARRAY_TAIL, kv);
|
||||
array_sort(section->kv, kv_sort, NULL);
|
||||
}
|
||||
else
|
||||
{ /* replace with the most recently read value */
|
||||
kv->value = value;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
DBG1(DBG_LIB, "parsing value failed near %s", *text);
|
||||
break;
|
||||
case '#':
|
||||
parse(text, "", "\n", NULL, &value);
|
||||
continue;
|
||||
default:
|
||||
finished = TRUE;
|
||||
continue;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a file and add the settings to the given section.
|
||||
*/
|
||||
static bool parse_file(linked_list_t *contents, char *file, int level,
|
||||
section_t *section)
|
||||
{
|
||||
bool success;
|
||||
char *text, *pos;
|
||||
struct stat st;
|
||||
FILE *fd;
|
||||
int len;
|
||||
|
||||
DBG2(DBG_LIB, "loading config file '%s'", file);
|
||||
if (stat(file, &st) == -1)
|
||||
{
|
||||
if (errno == ENOENT)
|
||||
{
|
||||
#ifdef STRONGSWAN_CONF
|
||||
if (streq(file, STRONGSWAN_CONF))
|
||||
{
|
||||
DBG2(DBG_LIB, "'%s' does not exist, ignored", file);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
DBG1(DBG_LIB, "'%s' does not exist, ignored", file);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
DBG1(DBG_LIB, "failed to stat '%s': %s", file, strerror(errno));
|
||||
return FALSE;
|
||||
}
|
||||
else if (!S_ISREG(st.st_mode))
|
||||
{
|
||||
DBG1(DBG_LIB, "'%s' is not a regular file", file);
|
||||
return FALSE;
|
||||
}
|
||||
fd = fopen(file, "r");
|
||||
if (fd == NULL)
|
||||
{
|
||||
DBG1(DBG_LIB, "'%s' is not readable", file);
|
||||
return FALSE;
|
||||
}
|
||||
fseek(fd, 0, SEEK_END);
|
||||
len = ftell(fd);
|
||||
rewind(fd);
|
||||
text = malloc(len + 2);
|
||||
text[len] = text[len + 1] = '\0';
|
||||
if (fread(text, 1, len, fd) != len)
|
||||
{
|
||||
free(text);
|
||||
fclose(fd);
|
||||
return FALSE;
|
||||
}
|
||||
fclose(fd);
|
||||
|
||||
pos = text;
|
||||
success = parse_section(contents, file, level, &pos, section);
|
||||
if (!success)
|
||||
{
|
||||
free(text);
|
||||
}
|
||||
else
|
||||
{
|
||||
contents->insert_last(contents, text);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the files matching "pattern", which is resolved with glob(3), if
|
||||
* available.
|
||||
* If the pattern is relative, the directory of "file" is used as base.
|
||||
*/
|
||||
static bool parse_files(linked_list_t *contents, char *file, int level,
|
||||
char *pattern, section_t *section)
|
||||
{
|
||||
bool success = TRUE;
|
||||
char pat[PATH_MAX];
|
||||
|
||||
if (level > MAX_INCLUSION_LEVEL)
|
||||
{
|
||||
DBG1(DBG_LIB, "maximum level of %d includes reached, ignored",
|
||||
MAX_INCLUSION_LEVEL);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!strlen(pattern))
|
||||
{
|
||||
DBG1(DBG_LIB, "empty include pattern, ignored");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!file || pattern[0] == '/')
|
||||
{ /* absolute path */
|
||||
if (snprintf(pat, sizeof(pat), "%s", pattern) >= sizeof(pat))
|
||||
{
|
||||
DBG1(DBG_LIB, "include pattern too long, ignored");
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{ /* base relative paths to the directory of the current file */
|
||||
char *dir = path_dirname(file);
|
||||
if (snprintf(pat, sizeof(pat), "%s/%s", dir, pattern) >= sizeof(pat))
|
||||
{
|
||||
DBG1(DBG_LIB, "include pattern too long, ignored");
|
||||
free(dir);
|
||||
return TRUE;
|
||||
}
|
||||
free(dir);
|
||||
}
|
||||
#ifdef HAVE_GLOB_H
|
||||
{
|
||||
int status;
|
||||
glob_t buf;
|
||||
|
||||
status = glob(pat, GLOB_ERR, NULL, &buf);
|
||||
if (status == GLOB_NOMATCH)
|
||||
{
|
||||
DBG1(DBG_LIB, "no files found matching '%s', ignored", pat);
|
||||
}
|
||||
else if (status != 0)
|
||||
{
|
||||
DBG1(DBG_LIB, "expanding file pattern '%s' failed", pat);
|
||||
success = FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
char **expanded;
|
||||
for (expanded = buf.gl_pathv; *expanded != NULL; expanded++)
|
||||
{
|
||||
success &= parse_file(contents, *expanded, level + 1, section);
|
||||
if (!success)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
globfree(&buf);
|
||||
}
|
||||
#else /* HAVE_GLOB_H */
|
||||
/* if glob(3) is not available, try to load pattern directly */
|
||||
success = parse_file(contents, pat, level + 1, section);
|
||||
#endif /* HAVE_GLOB_H */
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursivly extends "base" with "extension".
|
||||
*/
|
||||
static void section_extend(section_t *base, section_t *extension)
|
||||
{
|
||||
enumerator_t *enumerator;
|
||||
section_t *sec;
|
||||
kv_t *kv;
|
||||
|
||||
enumerator = array_create_enumerator(extension->sections);
|
||||
while (enumerator->enumerate(enumerator, (void**)&sec))
|
||||
{
|
||||
section_t *found;
|
||||
if (array_bsearch(base->sections, sec->name, section_find,
|
||||
&found) != -1)
|
||||
{
|
||||
section_extend(found, sec);
|
||||
}
|
||||
else
|
||||
{
|
||||
array_remove_at(extension->sections, enumerator);
|
||||
array_insert_create(&base->sections, ARRAY_TAIL, sec);
|
||||
array_sort(base->sections, section_sort, NULL);
|
||||
}
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
|
||||
enumerator = array_create_enumerator(extension->kv);
|
||||
while (enumerator->enumerate(enumerator, (void**)&kv))
|
||||
{
|
||||
kv_t *found;
|
||||
if (array_bsearch(base->kv, kv->key, kv_find, &found) != -1)
|
||||
{
|
||||
found->value = kv->value;
|
||||
}
|
||||
else
|
||||
{
|
||||
array_remove_at(extension->kv, enumerator);
|
||||
array_insert_create(&base->kv, ARRAY_TAIL, kv);
|
||||
array_sort(base->kv, kv_sort, NULL);
|
||||
}
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load settings from files matching the given file pattern.
|
||||
* All sections and values are added relative to "parent".
|
||||
* All files (even included ones) have to be loaded successfully.
|
||||
* If merge is FALSE the contents of parent are replaced with the parsed
|
||||
* contents, otherwise they are merged together.
|
||||
*/
|
||||
static bool load_files_internal(private_settings_t *this, section_t *parent,
|
||||
char *pattern, bool merge)
|
||||
{
|
||||
char *text;
|
||||
linked_list_t *contents;
|
||||
section_t *section;
|
||||
|
||||
if (pattern == NULL)
|
||||
{
|
||||
#ifdef STRONGSWAN_CONF
|
||||
pattern = STRONGSWAN_CONF;
|
||||
#else
|
||||
if (pattern == NULL || !pattern[0])
|
||||
{ /* TODO: Clear parent if merge is FALSE? */
|
||||
return FALSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
contents = linked_list_create();
|
||||
section = section_create(NULL);
|
||||
|
||||
if (!parse_files(contents, NULL, 0, pattern, section))
|
||||
section = settings_section_create(NULL);
|
||||
if (!settings_parser_parse_file(section, pattern))
|
||||
{
|
||||
contents->destroy_function(contents, (void*)free);
|
||||
section_destroy(section);
|
||||
settings_section_destroy(section, NULL);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
this->lock->write_lock(this->lock);
|
||||
if (!merge)
|
||||
{
|
||||
section_purge(parent);
|
||||
}
|
||||
/* extend parent section */
|
||||
section_extend(parent, section);
|
||||
/* move contents of loaded files to main store */
|
||||
while (contents->remove_first(contents, (void**)&text) == SUCCESS)
|
||||
{
|
||||
this->contents->insert_last(this->contents, text);
|
||||
}
|
||||
settings_section_extend(parent, section, this->contents, !merge);
|
||||
this->lock->unlock(this->lock);
|
||||
|
||||
section_destroy(section);
|
||||
contents->destroy(contents);
|
||||
settings_section_destroy(section, NULL);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -1476,8 +885,8 @@ METHOD(settings_t, load_files_section, bool,
|
||||
METHOD(settings_t, destroy, void,
|
||||
private_settings_t *this)
|
||||
{
|
||||
section_destroy(this->top);
|
||||
this->contents->destroy_function(this->contents, (void*)free);
|
||||
settings_section_destroy(this->top, NULL);
|
||||
array_destroy_function(this->contents, (void*)free, NULL);
|
||||
this->lock->destroy(this->lock);
|
||||
free(this);
|
||||
}
|
||||
@@ -1509,8 +918,8 @@ settings_t *settings_create(char *file)
|
||||
.load_files_section = _load_files_section,
|
||||
.destroy = _destroy,
|
||||
},
|
||||
.top = section_create(NULL),
|
||||
.contents = linked_list_create(),
|
||||
.top = settings_section_create(NULL),
|
||||
.contents = array_create(0, 0),
|
||||
.lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
|
||||
);
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
/**
|
||||
* @defgroup settings settings
|
||||
* @{ @ingroup utils
|
||||
* @ingroup libstrongswan
|
||||
*
|
||||
* @defgroup settings_t settings
|
||||
* @{ @ingroup settings
|
||||
*/
|
||||
|
||||
#ifndef SETTINGS_H_
|
||||
@@ -24,7 +27,7 @@
|
||||
|
||||
typedef struct settings_t settings_t;
|
||||
|
||||
#include "utils.h"
|
||||
#include "utils/utils.h"
|
||||
#include "collections/enumerator.h"
|
||||
|
||||
/**
|
||||
@@ -340,7 +343,9 @@ struct settings_t {
|
||||
/**
|
||||
* Load settings from a file.
|
||||
*
|
||||
* @param file file to read settings from, NULL for default
|
||||
* @note If parsing the file fails the object is still created.
|
||||
*
|
||||
* @param file optional file to read settings from
|
||||
* @return settings object
|
||||
*/
|
||||
settings_t *settings_create(char *file);
|
||||
@@ -0,0 +1,201 @@
|
||||
%{
|
||||
/*
|
||||
* Copyright (C) 2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include <utils/parser_helper.h>
|
||||
|
||||
#include "settings_parser.h"
|
||||
|
||||
bool settings_parser_open_next_file(parser_helper_t *ctx);
|
||||
|
||||
static void include_files(parser_helper_t *ctx);
|
||||
|
||||
%}
|
||||
%option debug
|
||||
%option warn
|
||||
|
||||
/* use start conditions stack */
|
||||
%option stack
|
||||
|
||||
/* do not declare unneded functions */
|
||||
%option noinput noyywrap
|
||||
|
||||
/* don't use global variables, and interact properly with bison */
|
||||
%option reentrant bison-bridge
|
||||
|
||||
/* maintain the line number */
|
||||
%option yylineno
|
||||
|
||||
/* don't generate a default rule */
|
||||
%option nodefault
|
||||
|
||||
/* prefix function/variable declarations */
|
||||
%option prefix="settings_parser_"
|
||||
/* don't change the name of the output file otherwise autotools has issues */
|
||||
%option outfile="lex.yy.c"
|
||||
|
||||
/* type of our extra data */
|
||||
%option extra-type="parser_helper_t*"
|
||||
|
||||
/* state used to scan include file patterns */
|
||||
%x inc
|
||||
/* state used to scan quoted strings */
|
||||
%x str
|
||||
|
||||
%%
|
||||
|
||||
[\t ]*#[^\n]* /* eat comments */
|
||||
[\t ]+ /* eat whitespace */
|
||||
\n|#.*\n return NEWLINE; /* also eats comments at the end of a line */
|
||||
|
||||
"{" |
|
||||
"}" |
|
||||
"=" return yytext[0];
|
||||
|
||||
"include"[\t ]+/[^=] {
|
||||
yyextra->string_init(yyextra);
|
||||
yy_push_state(inc, yyscanner);
|
||||
}
|
||||
|
||||
"\"" {
|
||||
yyextra->string_init(yyextra);
|
||||
yy_push_state(str, yyscanner);
|
||||
}
|
||||
|
||||
[^#{}="\n\t ]+ {
|
||||
yylval->s = strdup(yytext);
|
||||
return NAME;
|
||||
}
|
||||
|
||||
<inc>{
|
||||
/* we allow all characters except #, } and spaces, they can be escaped */
|
||||
<<EOF>> |
|
||||
[#}\n\t ] {
|
||||
if (*yytext)
|
||||
{
|
||||
switch (yytext[0])
|
||||
{
|
||||
case '\n':
|
||||
/* put the newline back to fix the line numbers */
|
||||
unput('\n');
|
||||
yy_set_bol(0);
|
||||
break;
|
||||
case '#':
|
||||
case '}':
|
||||
/* these are parsed outside of this start condition */
|
||||
unput(yytext[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
include_files(yyextra);
|
||||
yy_pop_state(yyscanner);
|
||||
}
|
||||
"\"" { /* string include */
|
||||
yy_push_state(str, yyscanner);
|
||||
}
|
||||
\\ {
|
||||
yyextra->string_add(yyextra, yytext);
|
||||
}
|
||||
\\["#} ] {
|
||||
yyextra->string_add(yyextra, yytext+1);
|
||||
}
|
||||
[^"\\#}\n\t ]+ {
|
||||
yyextra->string_add(yyextra, yytext);
|
||||
}
|
||||
}
|
||||
|
||||
<str>{
|
||||
"\"" |
|
||||
<<EOF>> |
|
||||
\n |
|
||||
\\ {
|
||||
if (!streq(yytext, "\""))
|
||||
{
|
||||
if (streq(yytext, "\n"))
|
||||
{ /* put the newline back to fix the line numbers */
|
||||
unput('\n');
|
||||
yy_set_bol(0);
|
||||
}
|
||||
PARSER_DBG1(yyextra, "unterminated string detected");
|
||||
}
|
||||
if (yy_top_state(yyscanner) == inc)
|
||||
{ /* string include */
|
||||
include_files(yyextra);
|
||||
yy_pop_state(yyscanner);
|
||||
yy_pop_state(yyscanner);
|
||||
}
|
||||
else
|
||||
{
|
||||
yy_pop_state(yyscanner);
|
||||
yylval->s = yyextra->string_get(yyextra);
|
||||
return STRING;
|
||||
}
|
||||
}
|
||||
\\n yyextra->string_add(yyextra, "\n");
|
||||
\\r yyextra->string_add(yyextra, "\r");
|
||||
\\t yyextra->string_add(yyextra, "\t");
|
||||
\\b yyextra->string_add(yyextra, "\b");
|
||||
\\f yyextra->string_add(yyextra, "\f");
|
||||
\\(.|\n) {
|
||||
yyextra->string_add(yyextra, yytext+1);
|
||||
}
|
||||
[^\\\n"]+ {
|
||||
yyextra->string_add(yyextra, yytext);
|
||||
}
|
||||
}
|
||||
|
||||
<<EOF>> {
|
||||
settings_parser_pop_buffer_state(yyscanner);
|
||||
if (!settings_parser_open_next_file(yyextra) && !YY_CURRENT_BUFFER)
|
||||
{
|
||||
yyterminate();
|
||||
}
|
||||
}
|
||||
|
||||
%%
|
||||
|
||||
/**
|
||||
* Open the next file, if any is queued and readable, otherwise returns FALSE.
|
||||
*/
|
||||
bool settings_parser_open_next_file(parser_helper_t *ctx)
|
||||
{
|
||||
FILE *file;
|
||||
|
||||
file = ctx->file_next(ctx);
|
||||
if (!file)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
settings_parser_set_in(file, ctx->scanner);
|
||||
settings_parser_push_buffer_state(
|
||||
settings_parser__create_buffer(file, YY_BUF_SIZE,
|
||||
ctx->scanner), ctx->scanner);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assumes that the file pattern to include is currently stored as string on
|
||||
* the helper object.
|
||||
*/
|
||||
static void include_files(parser_helper_t *ctx)
|
||||
{
|
||||
char *pattern = ctx->string_get(ctx);
|
||||
|
||||
ctx->file_include(ctx, pattern);
|
||||
free(pattern);
|
||||
|
||||
settings_parser_open_next_file(ctx);
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
%{
|
||||
/*
|
||||
* Copyright (C) 2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE /* for asprintf() */
|
||||
#include <stdio.h>
|
||||
|
||||
#include <library.h>
|
||||
#include <collections/array.h>
|
||||
#include <settings/settings_types.h>
|
||||
#include <utils/parser_helper.h>
|
||||
|
||||
#include "settings_parser.h"
|
||||
|
||||
#define YYDEBUG 1
|
||||
|
||||
/**
|
||||
* Defined by the lexer
|
||||
*/
|
||||
int settings_parser_lex(YYSTYPE *lvalp, void *scanner);
|
||||
int settings_parser_lex_init_extra(parser_helper_t *extra, void *scanner);
|
||||
int settings_parser_lex_destroy(void *scanner);
|
||||
int settings_parser_set_in(FILE *in, void *scanner);
|
||||
void settings_parser_set_debug(int debug, void *scanner);
|
||||
char *settings_parser_get_text(void *scanner);
|
||||
int settings_parser_get_leng(void *scanner);
|
||||
int settings_parser_get_lineno(void *scanner);
|
||||
/* Custom functions in lexer */
|
||||
bool settings_parser_open_next_file(parser_helper_t *ctx);
|
||||
|
||||
/**
|
||||
* Forward declarations
|
||||
*/
|
||||
static void settings_parser_error(parser_helper_t *ctx, const char *s);
|
||||
static section_t *push_section(parser_helper_t *ctx, char *name);
|
||||
static section_t *pop_section(parser_helper_t *ctx);
|
||||
static void add_section(parser_helper_t *ctx, section_t *section);
|
||||
static void add_setting(parser_helper_t *ctx, kv_t *kv);
|
||||
|
||||
/**
|
||||
* Make sure to call lexer with the proper context
|
||||
*/
|
||||
#undef yylex
|
||||
static int yylex(YYSTYPE *lvalp, parser_helper_t *ctx)
|
||||
{
|
||||
return settings_parser_lex(lvalp, ctx->scanner);
|
||||
}
|
||||
|
||||
%}
|
||||
%debug
|
||||
|
||||
/* generate verbose error messages */
|
||||
%error-verbose
|
||||
/* generate a reentrant parser */
|
||||
%define api.pure
|
||||
/* prefix function/variable declarations */
|
||||
%name-prefix "settings_parser_"
|
||||
|
||||
/* interact properly with the reentrant lexer */
|
||||
%lex-param {parser_helper_t *ctx}
|
||||
%parse-param {parser_helper_t *ctx}
|
||||
|
||||
/* types for terminal symbols... (can't use the typedef'd types) */
|
||||
%union {
|
||||
char *s;
|
||||
struct section_t *sec;
|
||||
struct kv_t *kv;
|
||||
}
|
||||
%token <s> NAME STRING
|
||||
%token NEWLINE
|
||||
|
||||
/* ...and other symbols */
|
||||
%type <s> value valuepart
|
||||
%type <sec> section_start section
|
||||
%type <kv> setting
|
||||
|
||||
/* properly destroy string tokens that are strdup()ed on error */
|
||||
%destructor { free($$); } NAME STRING value valuepart
|
||||
/* properly destroy parse results on error */
|
||||
%destructor { pop_section(ctx); settings_section_destroy($$, NULL); } section_start section
|
||||
%destructor { settings_kv_destroy($$, NULL); } setting
|
||||
|
||||
/* there are two shift/reduce conflicts because of the "NAME = NAME" and
|
||||
* "NAME {" ambiguity, and the "NAME =" rule) */
|
||||
%expect 2
|
||||
|
||||
%%
|
||||
|
||||
/**
|
||||
* strongswan.conf grammar rules
|
||||
*/
|
||||
statements:
|
||||
/* empty */
|
||||
| statements NEWLINE
|
||||
| statements statement
|
||||
;
|
||||
|
||||
statement:
|
||||
section
|
||||
{
|
||||
add_section(ctx, $section);
|
||||
}
|
||||
| setting
|
||||
{
|
||||
add_setting(ctx, $setting);
|
||||
}
|
||||
;
|
||||
|
||||
section:
|
||||
section_start statements '}'
|
||||
{
|
||||
pop_section(ctx);
|
||||
$$ = $section_start;
|
||||
}
|
||||
;
|
||||
|
||||
section_start:
|
||||
NAME '{'
|
||||
{
|
||||
$$ = push_section(ctx, $NAME);
|
||||
}
|
||||
|
|
||||
NAME NEWLINE '{'
|
||||
{
|
||||
$$ = push_section(ctx, $NAME);
|
||||
}
|
||||
;
|
||||
|
||||
setting:
|
||||
NAME '=' value
|
||||
{
|
||||
$$ = settings_kv_create($NAME, $value);
|
||||
}
|
||||
|
|
||||
NAME '='
|
||||
{
|
||||
$$ = settings_kv_create($NAME, NULL);
|
||||
}
|
||||
;
|
||||
|
||||
value:
|
||||
valuepart
|
||||
| value valuepart
|
||||
{ /* just put a single space between them, use strings for more */
|
||||
if (asprintf(&$$, "%s %s", $1, $2) < 0)
|
||||
{
|
||||
free($1);
|
||||
free($2);
|
||||
YYERROR;
|
||||
}
|
||||
free($1);
|
||||
free($2);
|
||||
}
|
||||
;
|
||||
|
||||
valuepart:
|
||||
NAME
|
||||
| STRING
|
||||
;
|
||||
|
||||
%%
|
||||
|
||||
/**
|
||||
* Referenced by the generated parser
|
||||
*/
|
||||
static void settings_parser_error(parser_helper_t *ctx, const char *s)
|
||||
{
|
||||
char *text = settings_parser_get_text(ctx->scanner);
|
||||
int len = settings_parser_get_leng(ctx->scanner);
|
||||
|
||||
if (len && text[len-1] == '\n')
|
||||
{ /* cut off newline at the end to avoid muti-line log messages */
|
||||
len--;
|
||||
}
|
||||
PARSER_DBG1(ctx, "%s [%.*s]", s, len, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a section and push it to the stack (the name is adopted), returns
|
||||
* the created section
|
||||
*/
|
||||
static section_t *push_section(parser_helper_t *ctx, char *name)
|
||||
{
|
||||
array_t *sections = (array_t*)ctx->context;
|
||||
section_t *section;
|
||||
|
||||
section = settings_section_create(name);
|
||||
array_insert(sections, ARRAY_TAIL, section);
|
||||
return section;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the top section of the stack and returns it
|
||||
*/
|
||||
static section_t *pop_section(parser_helper_t *ctx)
|
||||
{
|
||||
array_t *sections = (array_t*)ctx->context;
|
||||
section_t *section;
|
||||
|
||||
array_remove(sections, ARRAY_TAIL, §ion);
|
||||
return section;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given section to the section on top of the stack
|
||||
*/
|
||||
static void add_section(parser_helper_t *ctx, section_t *section)
|
||||
{
|
||||
array_t *sections = (array_t*)ctx->context;
|
||||
section_t *parent;
|
||||
|
||||
array_get(sections, ARRAY_TAIL, &parent);
|
||||
settings_section_add(parent, section, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given key/value pair to the section on top of the stack
|
||||
*/
|
||||
static void add_setting(parser_helper_t *ctx, kv_t *kv)
|
||||
{
|
||||
array_t *sections = (array_t*)ctx->context;
|
||||
section_t *section;
|
||||
|
||||
array_get(sections, ARRAY_TAIL, §ion);
|
||||
settings_kv_add(section, kv, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given file and add all sections and key/value pairs to the
|
||||
* given section.
|
||||
*/
|
||||
bool settings_parser_parse_file(section_t *root, char *name)
|
||||
{
|
||||
parser_helper_t *helper;
|
||||
array_t *sections = NULL;
|
||||
bool success = FALSE;
|
||||
|
||||
array_insert_create(§ions, ARRAY_TAIL, root);
|
||||
helper = parser_helper_create(sections);
|
||||
helper->get_lineno = settings_parser_get_lineno;
|
||||
if (settings_parser_lex_init_extra(helper, &helper->scanner) != 0)
|
||||
{
|
||||
helper->destroy(helper);
|
||||
array_destroy(sections);
|
||||
return FALSE;
|
||||
}
|
||||
helper->file_include(helper, name);
|
||||
if (!settings_parser_open_next_file(helper))
|
||||
{
|
||||
#ifdef STRONGSWAN_CONF
|
||||
if (streq(name, STRONGSWAN_CONF))
|
||||
{
|
||||
DBG2(DBG_CFG, "failed to open config file '%s'", name);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
DBG1(DBG_CFG, "failed to open config file '%s'", name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (getenv("DEBUG_SETTINGS_PARSER"))
|
||||
{
|
||||
yydebug = 1;
|
||||
settings_parser_set_debug(1, helper->scanner);
|
||||
}
|
||||
success = yyparse(helper) == 0;
|
||||
if (!success)
|
||||
{
|
||||
DBG1(DBG_CFG, "invalid config file '%s'", name);
|
||||
}
|
||||
}
|
||||
array_destroy(sections);
|
||||
settings_parser_lex_destroy(helper->scanner);
|
||||
helper->destroy(helper);
|
||||
return success;
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include "settings_types.h"
|
||||
|
||||
/*
|
||||
* Described in header
|
||||
*/
|
||||
kv_t *settings_kv_create(char *key, char *value)
|
||||
{
|
||||
kv_t *this;
|
||||
|
||||
INIT(this,
|
||||
.key = key,
|
||||
.value = value,
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in header
|
||||
*/
|
||||
void settings_kv_destroy(kv_t *this, array_t *contents)
|
||||
{
|
||||
free(this->key);
|
||||
if (contents && this->value)
|
||||
{
|
||||
array_insert(contents, ARRAY_TAIL, this->value);
|
||||
}
|
||||
else
|
||||
{
|
||||
free(this->value);
|
||||
}
|
||||
free(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in header
|
||||
*/
|
||||
section_t *settings_section_create(char *name)
|
||||
{
|
||||
section_t *this;
|
||||
|
||||
INIT(this,
|
||||
.name = name,
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
static void section_destroy(section_t *section, int idx, array_t *contents)
|
||||
{
|
||||
settings_section_destroy(section, contents);
|
||||
}
|
||||
|
||||
static void kv_destroy(kv_t *kv, int idx, array_t *contents)
|
||||
{
|
||||
settings_kv_destroy(kv, contents);
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in header
|
||||
*/
|
||||
void settings_section_destroy(section_t *this, array_t *contents)
|
||||
{
|
||||
array_destroy_function(this->sections, (void*)section_destroy, contents);
|
||||
array_destroy(this->sections_order);
|
||||
array_destroy_function(this->kv, (void*)kv_destroy, contents);
|
||||
array_destroy(this->kv_order);
|
||||
array_destroy(this->fallbacks);
|
||||
free(this->name);
|
||||
free(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in header
|
||||
*/
|
||||
void settings_kv_set(kv_t *kv, char *value, array_t *contents)
|
||||
{
|
||||
if (value && kv->value && streq(value, kv->value))
|
||||
{ /* no update required */
|
||||
free(value);
|
||||
return;
|
||||
}
|
||||
|
||||
/* if the new value was shorter we could overwrite the existing one but that
|
||||
* could lead to reads of partially updated values from other threads that
|
||||
* have a pointer to the existing value, so we replace it anyway */
|
||||
if (kv->value && contents)
|
||||
{
|
||||
array_insert(contents, ARRAY_TAIL, kv->value);
|
||||
}
|
||||
else
|
||||
{
|
||||
free(kv->value);
|
||||
}
|
||||
kv->value = value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in header
|
||||
*/
|
||||
void settings_kv_add(section_t *section, kv_t *kv, array_t *contents)
|
||||
{
|
||||
kv_t *found;
|
||||
|
||||
if (array_bsearch(section->kv, kv->key, settings_kv_find, &found) == -1)
|
||||
{
|
||||
array_insert_create(§ion->kv, ARRAY_TAIL, kv);
|
||||
array_sort(section->kv, settings_kv_sort, NULL);
|
||||
array_insert_create(§ion->kv_order, ARRAY_TAIL, kv);
|
||||
}
|
||||
else
|
||||
{
|
||||
settings_kv_set(found, kv->value, contents);
|
||||
kv->value = NULL;
|
||||
settings_kv_destroy(kv, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a section to the given parent, optionally remove settings/subsections
|
||||
* not found when extending an existing section
|
||||
*/
|
||||
static void add_section(section_t *parent, section_t *section,
|
||||
array_t *contents, bool purge)
|
||||
{
|
||||
section_t *found;
|
||||
|
||||
if (array_bsearch(parent->sections, section->name, settings_section_find,
|
||||
&found) == -1)
|
||||
{
|
||||
array_insert_create(&parent->sections, ARRAY_TAIL, section);
|
||||
array_sort(parent->sections, settings_section_sort, NULL);
|
||||
array_insert_create(&parent->sections_order, ARRAY_TAIL, section);
|
||||
}
|
||||
else
|
||||
{
|
||||
settings_section_extend(found, section, contents, purge);
|
||||
settings_section_destroy(section, contents);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in header
|
||||
*/
|
||||
void settings_section_add(section_t *parent, section_t *section,
|
||||
array_t *contents)
|
||||
{
|
||||
add_section(parent, section, contents, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge contents of a section, returns TRUE if section can be safely removed.
|
||||
*/
|
||||
static bool section_purge(section_t *this, array_t *contents)
|
||||
{
|
||||
section_t *current;
|
||||
int i, idx;
|
||||
|
||||
array_destroy_function(this->kv, (void*)kv_destroy, contents);
|
||||
this->kv = NULL;
|
||||
array_destroy(this->kv_order);
|
||||
this->kv_order = NULL;
|
||||
/* we ensure sections used as fallback, or configured with fallbacks (or
|
||||
* having any such subsections) are not removed */
|
||||
for (i = array_count(this->sections_order) - 1; i >= 0; i--)
|
||||
{
|
||||
array_get(this->sections, i, ¤t);
|
||||
if (section_purge(current, contents))
|
||||
{
|
||||
array_remove(this->sections_order, i, NULL);
|
||||
idx = array_bsearch(this->sections, current->name,
|
||||
settings_section_find, NULL);
|
||||
array_remove(this->sections, idx, NULL);
|
||||
settings_section_destroy(current, contents);
|
||||
}
|
||||
}
|
||||
return !this->fallbacks && !array_count(this->sections);
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in header
|
||||
*/
|
||||
void settings_section_extend(section_t *base, section_t *extension,
|
||||
array_t *contents, bool purge)
|
||||
{
|
||||
enumerator_t *enumerator;
|
||||
section_t *section;
|
||||
kv_t *kv;
|
||||
array_t *sections = NULL, *kvs = NULL;
|
||||
int idx;
|
||||
|
||||
if (purge)
|
||||
{ /* remove sections and settings in base not found in extension, the
|
||||
* others are removed too (from the _order list) so they can be inserted
|
||||
* in the order found in extension */
|
||||
enumerator = array_create_enumerator(base->sections_order);
|
||||
while (enumerator->enumerate(enumerator, (void**)§ion))
|
||||
{
|
||||
if (array_bsearch(extension->sections, section->name,
|
||||
settings_section_find, NULL) == -1)
|
||||
{
|
||||
idx = array_bsearch(base->sections, section->name,
|
||||
settings_section_find, NULL);
|
||||
if (section_purge(section, contents))
|
||||
{ /* only remove them if we can purge them */
|
||||
array_remove(base->sections, idx, NULL);
|
||||
array_remove_at(base->sections_order, enumerator);
|
||||
settings_section_destroy(section, contents);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
array_remove_at(base->sections_order, enumerator);
|
||||
array_insert_create(§ions, ARRAY_TAIL, section);
|
||||
array_sort(sections, settings_section_sort, NULL);
|
||||
}
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
|
||||
while (array_remove(base->kv_order, 0, &kv))
|
||||
{
|
||||
if (array_bsearch(extension->kv, kv->key, settings_kv_find,
|
||||
NULL) == -1)
|
||||
{
|
||||
idx = array_bsearch(base->kv, kv->key, settings_kv_find, NULL);
|
||||
array_remove(base->kv, idx, NULL);
|
||||
settings_kv_destroy(kv, contents);
|
||||
}
|
||||
else
|
||||
{
|
||||
array_insert_create(&kvs, ARRAY_TAIL, kv);
|
||||
array_sort(kvs, settings_kv_sort, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (array_remove(extension->sections_order, 0, §ion))
|
||||
{
|
||||
idx = array_bsearch(sections, section->name,
|
||||
settings_section_find, NULL);
|
||||
if (idx != -1)
|
||||
{
|
||||
section_t *existing;
|
||||
|
||||
array_remove(sections, idx, &existing);
|
||||
array_insert(base->sections_order, ARRAY_TAIL, existing);
|
||||
}
|
||||
idx = array_bsearch(extension->sections, section->name,
|
||||
settings_section_find, NULL);
|
||||
array_remove(extension->sections, idx, NULL);
|
||||
add_section(base, section, contents, purge);
|
||||
}
|
||||
|
||||
while (array_remove(extension->kv_order, 0, &kv))
|
||||
{
|
||||
idx = array_bsearch(kvs, kv->key, settings_kv_find, NULL);
|
||||
if (idx != -1)
|
||||
{
|
||||
kv_t *existing;
|
||||
|
||||
array_remove(kvs, idx, &existing);
|
||||
array_insert(base->kv_order, ARRAY_TAIL, existing);
|
||||
}
|
||||
idx = array_bsearch(extension->kv, kv->key, settings_kv_find, NULL);
|
||||
array_remove(extension->kv, idx, NULL);
|
||||
settings_kv_add(base, kv, contents);
|
||||
}
|
||||
array_destroy(sections);
|
||||
array_destroy(kvs);
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in header
|
||||
*/
|
||||
int settings_section_find(const void *a, const void *b)
|
||||
{
|
||||
const char *key = a;
|
||||
const section_t *item = b;
|
||||
return strcmp(key, item->name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in header
|
||||
*/
|
||||
int settings_section_sort(const void *a, const void *b, void *user)
|
||||
{
|
||||
const section_t *sa = a, *sb = b;
|
||||
return strcmp(sa->name, sb->name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in header
|
||||
*/
|
||||
int settings_kv_find(const void *a, const void *b)
|
||||
{
|
||||
const char *key = a;
|
||||
const kv_t *item = b;
|
||||
return strcmp(key, item->key);
|
||||
}
|
||||
|
||||
/*
|
||||
* Described in header
|
||||
*/
|
||||
int settings_kv_sort(const void *a, const void *b, void *user)
|
||||
{
|
||||
const kv_t *kva = a, *kvb = b;
|
||||
return strcmp(kva->key, kvb->key);
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal data types and functions shared between the parser and t.
|
||||
*
|
||||
* @defgroup settings_types settings_types
|
||||
* @{ @ingroup settings
|
||||
*/
|
||||
|
||||
#ifndef SETTINGS_TYPES_H_
|
||||
#define SETTINGS_TYPES_H_
|
||||
|
||||
typedef struct kv_t kv_t;
|
||||
typedef struct section_t section_t;
|
||||
|
||||
#include "collections/array.h"
|
||||
|
||||
/**
|
||||
* Key/value pair.
|
||||
*/
|
||||
struct kv_t {
|
||||
|
||||
/**
|
||||
* Key string, relative, not the full name.
|
||||
*/
|
||||
char *key;
|
||||
|
||||
/**
|
||||
* Value as string.
|
||||
*/
|
||||
char *value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Section containing subsections and key value pairs.
|
||||
*/
|
||||
struct section_t {
|
||||
|
||||
/**
|
||||
* Name of the section.
|
||||
*/
|
||||
char *name;
|
||||
|
||||
/**
|
||||
* Fallback sections, as section_t.
|
||||
*/
|
||||
array_t *fallbacks;
|
||||
|
||||
/**
|
||||
* Subsections, as section_t.
|
||||
*/
|
||||
array_t *sections;
|
||||
|
||||
/**
|
||||
* Subsections in original order, as section_t (pointer to obj in sections).
|
||||
*/
|
||||
array_t *sections_order;
|
||||
|
||||
/**
|
||||
* Key value pairs, as kv_t.
|
||||
*/
|
||||
array_t *kv;
|
||||
|
||||
/**
|
||||
* Key value pairs in original order, as kv_t (pointer to obj in kv).
|
||||
*/
|
||||
array_t *kv_order;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a key/value pair.
|
||||
*
|
||||
* @param key key (gets adopted)
|
||||
* @param value value (gets adopted)
|
||||
* @return allocated key/value pair
|
||||
*/
|
||||
kv_t *settings_kv_create(char *key, char *value);
|
||||
|
||||
/**
|
||||
* Destroy a key/value pair.
|
||||
*
|
||||
* @param this key/value pair to destroy
|
||||
* @param contents optional array to store the value in
|
||||
*/
|
||||
void settings_kv_destroy(kv_t *this, array_t *contents);
|
||||
|
||||
/**
|
||||
* Set the value of the given key/value pair.
|
||||
*
|
||||
* @param kv key/value pair
|
||||
* @param value new value (gets adopted), may be NULL
|
||||
* @param contents optional array to store replaced values in
|
||||
*/
|
||||
void settings_kv_set(kv_t *kv, char *value, array_t *contents);
|
||||
|
||||
/**
|
||||
* Add the given key/value pair to the given section.
|
||||
*
|
||||
* @param section section to add pair to
|
||||
* @param kv key/value pair to add (gets adopted)
|
||||
* @param contents optional array to store replaced values in
|
||||
*/
|
||||
void settings_kv_add(section_t *section, kv_t *kv, array_t *contents);
|
||||
|
||||
/**
|
||||
* Create a section with the given name.
|
||||
*
|
||||
* @param name name (gets adopted)
|
||||
* @return allocated section
|
||||
*/
|
||||
section_t *settings_section_create(char *name);
|
||||
|
||||
/**
|
||||
* Destroy a section.
|
||||
*
|
||||
* @param this section to destroy
|
||||
* @param contents optional array to store values of removed key/value pairs
|
||||
*/
|
||||
void settings_section_destroy(section_t *this, array_t *contents);
|
||||
|
||||
/**
|
||||
* Add the given section to the given parent section.
|
||||
*
|
||||
* @param parent section to add section to
|
||||
* @param section section to add (gets adopted)
|
||||
* @param contents optional array to store replaced values in
|
||||
*/
|
||||
void settings_section_add(section_t *parent, section_t *section,
|
||||
array_t *contents);
|
||||
|
||||
/**
|
||||
* Extend the first section with the values and sub-sections of the second
|
||||
* section, from where they are consequently removed.
|
||||
*
|
||||
* @param base base section to extend
|
||||
* @param extension section whose data is extracted
|
||||
* @param contents optional array to store replaced values in
|
||||
* @param purge TRUE to remove settings and sections not found in the
|
||||
* extension (unless (sub-)sections have/are fallbacks)
|
||||
*/
|
||||
void settings_section_extend(section_t *base, section_t *extension,
|
||||
array_t *contents, bool purge);
|
||||
|
||||
/**
|
||||
* Callback to find a section by name
|
||||
*/
|
||||
int settings_section_find(const void *a, const void *b);
|
||||
|
||||
/**
|
||||
* Callback to sort sections by name
|
||||
*/
|
||||
int settings_section_sort(const void *a, const void *b, void *user);
|
||||
|
||||
/**
|
||||
* Callback to find a key/value pair by key
|
||||
*/
|
||||
int settings_kv_find(const void *a, const void *b);
|
||||
|
||||
/**
|
||||
* Callback to sort kv pairs by key
|
||||
*/
|
||||
int settings_kv_sort(const void *a, const void *b, void *user);
|
||||
|
||||
#endif /** SETTINGS_TYPES_H_ @}*/
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <utils/settings.h>
|
||||
#include <settings/settings.h>
|
||||
#include <utils/chunk.h>
|
||||
#include <utils/utils.h>
|
||||
#include <collections/linked_list.h>
|
||||
@@ -39,6 +39,7 @@ START_SETUP(setup_base_config)
|
||||
" # this gets overridden below\n"
|
||||
" key2 = val2\n"
|
||||
" none = \n"
|
||||
" empty = \"\"\n"
|
||||
" sub1 {\n"
|
||||
" key = value\n"
|
||||
" key2 = value2\n"
|
||||
@@ -51,7 +52,8 @@ START_SETUP(setup_base_config)
|
||||
" sub% {\n"
|
||||
" id = %any\n"
|
||||
" }\n"
|
||||
" key2 = with spaces\n"
|
||||
" key2 = with space\n"
|
||||
" key3 = \"string with\\nnewline\"\n"
|
||||
"}\n"
|
||||
"out = side\n"
|
||||
"other {\n"
|
||||
@@ -79,7 +81,9 @@ START_TEST(test_get_str)
|
||||
verify_string("val1", "main.key1");
|
||||
verify_string("val1", "main..key1");
|
||||
verify_string("val1", ".main.key1");
|
||||
verify_string("with spaces", "main.key2");
|
||||
verify_string("", "main.empty");
|
||||
verify_string("with space", "main.key2");
|
||||
verify_string("string with\nnewline", "main.key3");
|
||||
verify_string("value", "main.sub1.key");
|
||||
verify_string("value2", "main.sub1.key2");
|
||||
verify_string("bar", "main.sub1.subsub.foo");
|
||||
@@ -88,10 +92,8 @@ START_TEST(test_get_str)
|
||||
verify_string("side", "out");
|
||||
verify_string("other val", "other.key1");
|
||||
|
||||
/* FIXME: should this rather be undefined i.e. return the default value? */
|
||||
verify_string("", "main.none");
|
||||
|
||||
verify_null("main.key3");
|
||||
verify_null("main.none");
|
||||
verify_null("main.key4");
|
||||
verify_null("other.sub");
|
||||
}
|
||||
END_TEST
|
||||
@@ -125,16 +127,35 @@ START_TEST(test_get_str_printf)
|
||||
* probably document it at least */
|
||||
verify_null("main.%s%u.key%d", "sub", 1, 2);
|
||||
|
||||
verify_null("%s.%s%d", "main", "key", 3);
|
||||
verify_null("%s.%s%d", "main", "key", 4);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_set_str)
|
||||
{
|
||||
char *val1, *val2;
|
||||
|
||||
val1 = settings->get_str(settings, "main.key1", NULL);
|
||||
ck_assert_str_eq("val1", val1);
|
||||
settings->set_str(settings, "main.key1", "val");
|
||||
verify_string("val", "main.key1");
|
||||
/* the pointer we got before is still valid */
|
||||
ck_assert_str_eq("val1", val1);
|
||||
|
||||
val2 = settings->get_str(settings, "main.key1", NULL);
|
||||
ck_assert_str_eq("val", val2);
|
||||
settings->set_str(settings, "main.key1", "longer value");
|
||||
verify_string("longer value", "main.key1");
|
||||
/* the pointers we got before are still valid */
|
||||
ck_assert_str_eq("val1", val1);
|
||||
ck_assert_str_eq("val", val2);
|
||||
|
||||
val1 = settings->get_str(settings, "main.key1", NULL);
|
||||
settings->set_str(settings, "main.key1", "longer value");
|
||||
val2 = settings->get_str(settings, "main.key1", NULL);
|
||||
/* setting the same string should should get us the same pointer */
|
||||
ck_assert(val1 == val2);
|
||||
|
||||
settings->set_str(settings, "main", "main val");
|
||||
verify_string("main val", "main");
|
||||
settings->set_str(settings, "main.sub1.new", "added");
|
||||
@@ -183,6 +204,7 @@ START_SETUP(setup_bool_config)
|
||||
" key7 = disabled\n"
|
||||
" key8 = 0\n"
|
||||
" key9 = 5\n"
|
||||
" empty = \"\"\n"
|
||||
" none = \n"
|
||||
" foo = bar\n"
|
||||
"}"));
|
||||
@@ -203,6 +225,8 @@ START_TEST(test_get_bool)
|
||||
verify_bool(FALSE, TRUE, "main.key7");
|
||||
verify_bool(FALSE, TRUE, "main.key8");
|
||||
|
||||
verify_bool(FALSE, FALSE, "main.empty");
|
||||
verify_bool(TRUE, TRUE, "main.empty");
|
||||
verify_bool(FALSE, FALSE, "main.none");
|
||||
verify_bool(TRUE, TRUE, "main.none");
|
||||
verify_bool(FALSE, FALSE, "main.foo");
|
||||
@@ -240,6 +264,7 @@ START_SETUP(setup_int_config)
|
||||
" # gets cut off\n"
|
||||
" key2 = 5.5\n"
|
||||
" key3 = -42\n"
|
||||
" empty = \"\"\n"
|
||||
" none = \n"
|
||||
" foo1 = bar\n"
|
||||
" foo2 = bar13\n"
|
||||
@@ -257,8 +282,10 @@ START_TEST(test_get_int)
|
||||
verify_int(5, 0, "main.key2");
|
||||
verify_int(-42, 0, "main.key3");
|
||||
|
||||
verify_int(0, 11, "main.empty");
|
||||
verify_int(11, 11, "main.none");
|
||||
|
||||
/* FIXME: do we want this behavior? */
|
||||
verify_int(0, 11, "main.none");
|
||||
verify_int(0, 11, "main.foo1");
|
||||
verify_int(0, 11, "main.foo2");
|
||||
verify_int(13, 11, "main.foo3");
|
||||
@@ -291,6 +318,7 @@ START_SETUP(setup_double_config)
|
||||
" key2 = 5.5\n"
|
||||
" key3 = -42\n"
|
||||
" key4 = -42.5\n"
|
||||
" empty = \"\"\n"
|
||||
" none = \n"
|
||||
" foo1 = bar\n"
|
||||
" foo2 = bar13.5\n"
|
||||
@@ -309,8 +337,10 @@ START_TEST(test_get_double)
|
||||
verify_double(-42, 0, "main.key3");
|
||||
verify_double(-42.5, 0, "main.key4");
|
||||
|
||||
verify_double(0, 11.5, "main.empty");
|
||||
verify_double(11.5, 11.5, "main.none");
|
||||
|
||||
/* FIXME: do we want this behavior? */
|
||||
verify_double(0, 11.5, "main.none");
|
||||
verify_double(0, 11.5, "main.foo1");
|
||||
verify_double(0, 11.5, "main.foo2");
|
||||
verify_double(13.5, 11.5, "main.foo3");
|
||||
@@ -345,6 +375,7 @@ START_SETUP(setup_time_config)
|
||||
" key2 = 5m\n"
|
||||
" key3 = 5h\n"
|
||||
" key4 = 5d\n"
|
||||
" empty = \"\"\n"
|
||||
" none = \n"
|
||||
" foo1 = bar\n"
|
||||
" foo2 = bar13\n"
|
||||
@@ -363,8 +394,10 @@ START_TEST(test_get_time)
|
||||
verify_time(18000, 0, "main.key3");
|
||||
verify_time(432000, 0, "main.key4");
|
||||
|
||||
verify_time(0, 11, "main.empty");
|
||||
verify_time(11, 11, "main.none");
|
||||
|
||||
/* FIXME: do we want this behavior? */
|
||||
verify_time(0, 11, "main.none");
|
||||
verify_time(0, 11, "main.foo1");
|
||||
verify_time(0, 11, "main.foo2");
|
||||
verify_time(13, 11, "main.foo3");
|
||||
@@ -387,37 +420,21 @@ START_TEST(test_set_time)
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static bool verify_section(linked_list_t *verifier, char *section)
|
||||
{
|
||||
enumerator_t *enumerator;
|
||||
char *current;
|
||||
bool result = FALSE;
|
||||
|
||||
enumerator = verifier->create_enumerator(verifier);
|
||||
while (enumerator->enumerate(enumerator, ¤t))
|
||||
{
|
||||
if (streq(current, section))
|
||||
{
|
||||
verifier->remove_at(verifier, enumerator);
|
||||
result = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void verify_sections(linked_list_t *verifier, char *parent)
|
||||
{
|
||||
enumerator_t *enumerator;
|
||||
char *section;
|
||||
enumerator_t *enumerator, *ver;
|
||||
char *section, *current;
|
||||
|
||||
enumerator = settings->create_section_enumerator(settings, parent);
|
||||
while (enumerator->enumerate(enumerator, §ion))
|
||||
ver = verifier->create_enumerator(verifier);
|
||||
while (enumerator->enumerate(enumerator, §ion) &&
|
||||
ver->enumerate(ver, ¤t))
|
||||
{
|
||||
ck_assert(verify_section(verifier, section));
|
||||
ck_assert_str_eq(section, current);
|
||||
verifier->remove_at(verifier, ver);
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
ver->destroy(ver);
|
||||
ck_assert_int_eq(0, verifier->get_count(verifier));
|
||||
verifier->destroy(verifier);
|
||||
}
|
||||
@@ -429,8 +446,8 @@ START_TEST(test_section_enumerator)
|
||||
verifier = linked_list_create_with_items("sub1", "sub%", NULL);
|
||||
verify_sections(verifier, "main");
|
||||
|
||||
settings->set_str(settings, "main.sub2.new", "added");
|
||||
verifier = linked_list_create_with_items("sub1", "sub%", "sub2", NULL);
|
||||
settings->set_str(settings, "main.sub0.new", "added");
|
||||
verifier = linked_list_create_with_items("sub1", "sub%", "sub0", NULL);
|
||||
verify_sections(verifier, "main");
|
||||
|
||||
verifier = linked_list_create_with_items("subsub", NULL);
|
||||
@@ -447,44 +464,27 @@ START_TEST(test_section_enumerator)
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static bool verify_key_value(linked_list_t *keys, linked_list_t *values,
|
||||
char *key, char *value)
|
||||
{
|
||||
enumerator_t *enum_keys, *enum_values;
|
||||
char *current_key, *current_value;
|
||||
bool result = FALSE;
|
||||
|
||||
enum_keys = keys->create_enumerator(keys);
|
||||
enum_values = values->create_enumerator(values);
|
||||
while (enum_keys->enumerate(enum_keys, ¤t_key) &&
|
||||
enum_values->enumerate(enum_values, ¤t_value))
|
||||
{
|
||||
if (streq(current_key, key))
|
||||
{
|
||||
ck_assert_str_eq(current_value, value);
|
||||
keys->remove_at(keys, enum_keys);
|
||||
values->remove_at(values, enum_values);
|
||||
result = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
enum_keys->destroy(enum_keys);
|
||||
enum_values->destroy(enum_values);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void verify_key_values(linked_list_t *keys, linked_list_t *values,
|
||||
char *parent)
|
||||
{
|
||||
enumerator_t *enumerator;
|
||||
char *key, *value;
|
||||
enumerator_t *enumerator, *enum_keys, *enum_values;
|
||||
char *key, *value, *current_key, *current_value;
|
||||
|
||||
enumerator = settings->create_key_value_enumerator(settings, parent);
|
||||
while (enumerator->enumerate(enumerator, &key, &value))
|
||||
enum_keys = keys->create_enumerator(keys);
|
||||
enum_values = values->create_enumerator(values);
|
||||
while (enumerator->enumerate(enumerator, &key, &value) &&
|
||||
enum_keys->enumerate(enum_keys, ¤t_key) &&
|
||||
enum_values->enumerate(enum_values, ¤t_value))
|
||||
{
|
||||
ck_assert(verify_key_value(keys, values, key, value));
|
||||
ck_assert_str_eq(current_key, key);
|
||||
ck_assert_str_eq(current_value, value);
|
||||
keys->remove_at(keys, enum_keys);
|
||||
values->remove_at(values, enum_values);
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
enum_keys->destroy(enum_keys);
|
||||
enum_values->destroy(enum_values);
|
||||
ck_assert_int_eq(0, keys->get_count(keys));
|
||||
keys->destroy(keys);
|
||||
values->destroy(values);
|
||||
@@ -494,8 +494,8 @@ START_TEST(test_key_value_enumerator)
|
||||
{
|
||||
linked_list_t *keys, *values;
|
||||
|
||||
keys = linked_list_create_with_items("key1", "key2", "none", NULL);
|
||||
values = linked_list_create_with_items("val1", "with spaces", "", NULL);
|
||||
keys = linked_list_create_with_items("key1", "key2", "empty", "key3", NULL);
|
||||
values = linked_list_create_with_items("val1", "with space", "", "string with\nnewline", NULL);
|
||||
verify_key_values(keys, values, "main");
|
||||
|
||||
keys = linked_list_create_with_items("key", "key2", "subsub", NULL);
|
||||
@@ -531,6 +531,7 @@ START_SETUP(setup_include_config)
|
||||
"main {\n"
|
||||
" key1 = n1\n"
|
||||
" key2 = n2\n"
|
||||
" key3 = val3\n"
|
||||
" none = \n"
|
||||
" sub1 {\n"
|
||||
" key3 = value\n"
|
||||
@@ -563,13 +564,15 @@ static void verify_include()
|
||||
{
|
||||
verify_string("n1", "main.key1");
|
||||
verify_string("v2", "main.key2");
|
||||
verify_string("", "main.none");
|
||||
verify_string("val3", "main.key3");
|
||||
verify_string("val", "main.sub1.key");
|
||||
verify_string("v2", "main.sub1.key2");
|
||||
verify_string("val", "main.sub1.sub1.key");
|
||||
verify_string("value", "main.sub1.key3");
|
||||
verify_string("value", "main.sub1.include");
|
||||
verify_string("val3", "main.sub2.sub3");
|
||||
|
||||
verify_null("main.none");
|
||||
}
|
||||
|
||||
START_TEST(test_include)
|
||||
@@ -580,13 +583,13 @@ START_TEST(test_include)
|
||||
" key2 = val2\n"
|
||||
" none = x\n"
|
||||
" sub1 {\n"
|
||||
" include this/does/not/exist.conf\n"
|
||||
" include = value\n"
|
||||
" key2 = value2\n"
|
||||
" include " include2 "\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"# currently there must be a newline after include statements\n"
|
||||
"include " include1 "\n");
|
||||
"include " include1);
|
||||
|
||||
create_settings(contents);
|
||||
verify_include();
|
||||
@@ -599,6 +602,7 @@ START_TEST(test_load_files)
|
||||
"main {\n"
|
||||
" key1 = val1\n"
|
||||
" key2 = val2\n"
|
||||
" key3 = val3\n"
|
||||
" none = x\n"
|
||||
" sub1 {\n"
|
||||
" include = value\n"
|
||||
@@ -608,9 +612,35 @@ START_TEST(test_load_files)
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}");
|
||||
char *val1, *val2, *val3;
|
||||
|
||||
create_settings(contents);
|
||||
|
||||
val1 = settings->get_str(settings, "main.key1", NULL);
|
||||
val2 = settings->get_str(settings, "main.sub1.key2", NULL);
|
||||
/* loading the same file twice should not change anything, with... */
|
||||
ck_assert(settings->load_files(settings, path, TRUE));
|
||||
ck_assert(val1 == settings->get_str(settings, "main.key1", NULL));
|
||||
ck_assert(val2 == settings->get_str(settings, "main.sub1.key2", NULL));
|
||||
/* ...or without merging */
|
||||
ck_assert(settings->load_files(settings, path, FALSE));
|
||||
ck_assert(val1 == settings->get_str(settings, "main.key1", NULL));
|
||||
ck_assert(val2 == settings->get_str(settings, "main.sub1.key2", NULL));
|
||||
|
||||
val1 = settings->get_str(settings, "main.key2", NULL);
|
||||
val2 = settings->get_str(settings, "main.key3", NULL);
|
||||
val3 = settings->get_str(settings, "main.none", NULL);
|
||||
/* only pointers for modified settings should change, but still be valid */
|
||||
ck_assert(settings->load_files(settings, include1, FALSE));
|
||||
ck_assert(val1 != settings->get_str(settings, "main.key2", NULL));
|
||||
ck_assert_str_eq(val1, "val2");
|
||||
ck_assert(val2 == settings->get_str(settings, "main.key3", NULL));
|
||||
ck_assert(val3 != settings->get_str(settings, "main.none", NULL));
|
||||
ck_assert_str_eq(val3, "x");
|
||||
|
||||
settings->destroy(settings);
|
||||
create_settings(contents);
|
||||
|
||||
ck_assert(settings->load_files(settings, include1, TRUE));
|
||||
verify_include();
|
||||
|
||||
@@ -641,11 +671,11 @@ START_TEST(test_load_files_section)
|
||||
ck_assert(settings->load_files_section(settings, include2, TRUE, "main.sub1"));
|
||||
verify_include();
|
||||
|
||||
/* non existing files are no failure */
|
||||
ck_assert(settings->load_files_section(settings, include1".conf", TRUE, ""));
|
||||
/* non existing files are a failure here */
|
||||
ck_assert(!settings->load_files_section(settings, include1".conf", TRUE, ""));
|
||||
verify_include();
|
||||
|
||||
/* unreadable files are */
|
||||
/* unreadable files are too */
|
||||
ck_assert(chunk_write(contents, include1".no", 0444, TRUE));
|
||||
ck_assert(!settings->load_files_section(settings, include1".no", TRUE, ""));
|
||||
unlink(include1".no");
|
||||
@@ -664,6 +694,87 @@ START_TEST(test_load_files_section)
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_order_kv)
|
||||
{
|
||||
chunk_t base = chunk_from_str(
|
||||
"main {\n"
|
||||
" key1 = val1\n"
|
||||
" key2 = val2\n"
|
||||
" key3 = val3\n"
|
||||
"}");
|
||||
chunk_t include = chunk_from_str(
|
||||
"main {\n"
|
||||
" key0 = val0\n"
|
||||
" key3 = val3\n"
|
||||
" key1 = val1\n"
|
||||
"}");
|
||||
linked_list_t *keys, *values;
|
||||
|
||||
create_settings(base);
|
||||
ck_assert(chunk_write(include, include1, 0022, TRUE));
|
||||
|
||||
keys = linked_list_create_with_items("key1", "key2", "key3", NULL);
|
||||
values = linked_list_create_with_items("val1", "val2", "val3", NULL);
|
||||
verify_key_values(keys, values, "main");
|
||||
|
||||
/* the original order is maintained if the settings are merged */
|
||||
ck_assert(settings->load_files(settings, include1, TRUE));
|
||||
keys = linked_list_create_with_items("key1", "key2", "key3", "key0", NULL);
|
||||
values = linked_list_create_with_items("val1", "val2", "val3", "val0", NULL);
|
||||
verify_key_values(keys, values, "main");
|
||||
|
||||
/* but the new order is adopted if the settings are replaced */
|
||||
ck_assert(settings->load_files(settings, include1, FALSE));
|
||||
keys = linked_list_create_with_items("key0", "key3", "key1", NULL);
|
||||
values = linked_list_create_with_items("val0", "val3", "val1", NULL);
|
||||
verify_key_values(keys, values, "main");
|
||||
|
||||
unlink(include1);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_order_section)
|
||||
{
|
||||
chunk_t base = chunk_from_str(
|
||||
"main {\n"
|
||||
" sub1 {\n"
|
||||
" }\n"
|
||||
" sub2 {\n"
|
||||
" }\n"
|
||||
" sub3 {\n"
|
||||
" }\n"
|
||||
"}");
|
||||
chunk_t include = chunk_from_str(
|
||||
"main {\n"
|
||||
" sub0 {\n"
|
||||
" }\n"
|
||||
" sub3 {\n"
|
||||
" }\n"
|
||||
" sub1 {\n"
|
||||
" }\n"
|
||||
"}");
|
||||
linked_list_t *sections;
|
||||
|
||||
create_settings(base);
|
||||
ck_assert(chunk_write(include, include1, 0022, TRUE));
|
||||
|
||||
sections = linked_list_create_with_items("sub1", "sub2", "sub3", NULL);
|
||||
verify_sections(sections, "main");
|
||||
|
||||
/* the original order is maintained if the settings are merged */
|
||||
ck_assert(settings->load_files(settings, include1, TRUE));
|
||||
sections = linked_list_create_with_items("sub1", "sub2", "sub3", "sub0", NULL);
|
||||
verify_sections(sections, "main");
|
||||
|
||||
/* but the new order is adopted if the settings are replaced */
|
||||
ck_assert(settings->load_files(settings, include1, FALSE));
|
||||
sections = linked_list_create_with_items("sub0", "sub3", "sub1", NULL);
|
||||
verify_sections(sections, "main");
|
||||
|
||||
unlink(include1);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_SETUP(setup_fallback_config)
|
||||
{
|
||||
create_settings(chunk_from_str(
|
||||
@@ -781,57 +892,85 @@ START_TEST(test_add_fallback_printf)
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_SETUP(setup_invalid_config)
|
||||
START_SETUP(setup_string_config)
|
||||
{
|
||||
create_settings(chunk_from_str(
|
||||
"# section without name\n"
|
||||
"{\n"
|
||||
" key1 = val1\n"
|
||||
"}\n"
|
||||
"main {\n"
|
||||
" key2 = val2\n"
|
||||
" # value without key\n"
|
||||
" = val3\n"
|
||||
" key4 = val4\n"
|
||||
" # key without value does not change it\n"
|
||||
" key4\n"
|
||||
" # subsection without name\n"
|
||||
" {\n"
|
||||
" key5 = val5\n"
|
||||
" }\n"
|
||||
" # empty include pattern\n"
|
||||
" include\n"
|
||||
" key6 = val6\n"
|
||||
"}"));
|
||||
"string = \" with accurate\twhitespace\"\n"
|
||||
"special = \"all { special } characters # can be used.\"\n"
|
||||
"unterminated = \"is fine\n"
|
||||
"but = produces a warning\n"
|
||||
"newlines = \"can either be encoded\\nor\\\n"
|
||||
"escaped\"\n"
|
||||
"quotes = \"\\\"and\\\" slashes \\\\ can \\\\ be\" # escaped too\n"
|
||||
"multiple = \"strings\" are \"combined\"\n"
|
||||
));
|
||||
}
|
||||
END_SETUP
|
||||
|
||||
START_TEST(test_invalid)
|
||||
START_TEST(test_strings)
|
||||
{
|
||||
verify_string(" with accurate\twhitespace", "string");
|
||||
verify_string("all { special } characters # can be used.", "special");
|
||||
verify_string("is fine", "unterminated");
|
||||
verify_string("produces a warning", "but");
|
||||
verify_string("can either be encoded\nor\nescaped", "newlines");
|
||||
verify_string("\"and\" slashes \\ can \\ be", "quotes");
|
||||
verify_string("strings are combined", "multiple");
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_valid)
|
||||
{
|
||||
linked_list_t *keys, *values;
|
||||
chunk_t contents;
|
||||
|
||||
verify_null("key1");
|
||||
verify_null(".key1");
|
||||
verify_null("%s.key1", "");
|
||||
verify_string("val2", "main.key2");
|
||||
verify_string("val4", "main.key4");
|
||||
verify_null("main..key5");
|
||||
verify_string("val6", "main.key6");
|
||||
|
||||
keys = linked_list_create_with_items("main", NULL);
|
||||
verify_sections(keys, "");
|
||||
|
||||
keys = linked_list_create_with_items(NULL);
|
||||
verify_sections(keys, "main");
|
||||
|
||||
keys = linked_list_create_with_items("key2", "key4", "key6", NULL);
|
||||
values = linked_list_create_with_items("val2", "val4", "val6", NULL);
|
||||
verify_key_values(keys, values, "main");
|
||||
|
||||
/* FIXME: we should probably fix this */
|
||||
contents = chunk_from_str(
|
||||
"requires = newline");
|
||||
"single = value");
|
||||
ck_assert(chunk_write(contents, path, 0022, TRUE));
|
||||
ck_assert(settings->load_files(settings, path, FALSE));
|
||||
verify_string("value", "single");
|
||||
|
||||
contents = chunk_from_str(
|
||||
"singleline { single = value }");
|
||||
ck_assert(chunk_write(contents, path, 0022, TRUE));
|
||||
ck_assert(settings->load_files(settings, path, FALSE));
|
||||
verify_string("value", "singleline.single");
|
||||
|
||||
contents = chunk_from_str(
|
||||
"singleline { sub { sub1 = val1 } single = value }");
|
||||
ck_assert(chunk_write(contents, path, 0022, TRUE));
|
||||
ck_assert(settings->load_files(settings, path, FALSE));
|
||||
verify_string("val1", "singleline.sub.sub1");
|
||||
|
||||
contents = chunk_from_str(
|
||||
"newline\n { single = value }");
|
||||
ck_assert(chunk_write(contents, path, 0022, TRUE));
|
||||
ck_assert(settings->load_files(settings, path, FALSE));
|
||||
verify_string("value", "newline.single");
|
||||
|
||||
contents = chunk_from_str(
|
||||
"section {\n"
|
||||
" include # without pattern produces a warning, but is fine\n"
|
||||
"}\n");
|
||||
ck_assert(chunk_write(contents, path, 0022, TRUE));
|
||||
ck_assert(settings->load_files(settings, path, FALSE));
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_invalid)
|
||||
{
|
||||
chunk_t contents;
|
||||
|
||||
contents = chunk_from_str(
|
||||
"{\n"
|
||||
" no = section name\n"
|
||||
"}\n");
|
||||
ck_assert(chunk_write(contents, path, 0022, TRUE));
|
||||
ck_assert(!settings->load_files(settings, path, FALSE));
|
||||
|
||||
contents = chunk_from_str(
|
||||
"no {\n"
|
||||
" = key name\n"
|
||||
"}\n");
|
||||
ck_assert(chunk_write(contents, path, 0022, TRUE));
|
||||
ck_assert(!settings->load_files(settings, path, FALSE));
|
||||
|
||||
@@ -842,7 +981,12 @@ START_TEST(test_invalid)
|
||||
ck_assert(!settings->load_files(settings, path, FALSE));
|
||||
|
||||
contents = chunk_from_str(
|
||||
"singleline { not = valid }\n");
|
||||
"spaces in name {}");
|
||||
ck_assert(chunk_write(contents, path, 0022, TRUE));
|
||||
ck_assert(!settings->load_files(settings, path, FALSE));
|
||||
|
||||
contents = chunk_from_str(
|
||||
"only = a single setting = per line");
|
||||
ck_assert(chunk_write(contents, path, 0022, TRUE));
|
||||
ck_assert(!settings->load_files(settings, path, FALSE));
|
||||
}
|
||||
@@ -903,6 +1047,8 @@ Suite *settings_suite_create()
|
||||
tcase_add_test(tc, test_include);
|
||||
tcase_add_test(tc, test_load_files);
|
||||
tcase_add_test(tc, test_load_files_section);
|
||||
tcase_add_test(tc, test_order_kv);
|
||||
tcase_add_test(tc, test_order_section);
|
||||
suite_add_tcase(s, tc);
|
||||
|
||||
tc = tcase_create("fallback");
|
||||
@@ -911,8 +1057,14 @@ Suite *settings_suite_create()
|
||||
tcase_add_test(tc, test_add_fallback_printf);
|
||||
suite_add_tcase(s, tc);
|
||||
|
||||
tc = tcase_create("invalid data");
|
||||
tcase_add_checked_fixture(tc, setup_invalid_config, teardown_config);
|
||||
tc = tcase_create("strings");
|
||||
tcase_add_checked_fixture(tc, setup_string_config, teardown_config);
|
||||
tcase_add_test(tc, test_strings);
|
||||
suite_add_tcase(s, tc);
|
||||
|
||||
tc = tcase_create("valid/invalid data");
|
||||
tcase_add_checked_fixture(tc, setup_base_config, teardown_config);
|
||||
tcase_add_test(tc, test_valid);
|
||||
tcase_add_test(tc, test_invalid);
|
||||
suite_add_tcase(s, tc);
|
||||
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "parser_helper.h"
|
||||
|
||||
#include <collections/array.h>
|
||||
|
||||
typedef struct private_parser_helper_t private_parser_helper_t;
|
||||
typedef struct parser_helper_file_t parser_helper_file_t;
|
||||
|
||||
struct private_parser_helper_t {
|
||||
|
||||
/**
|
||||
* Public interface.
|
||||
*/
|
||||
parser_helper_t public;
|
||||
|
||||
/**
|
||||
* Stack of included files, as parser_helper_file_t.
|
||||
*/
|
||||
array_t *files;
|
||||
|
||||
/**
|
||||
* Helper for parsing strings.
|
||||
*/
|
||||
bio_writer_t *writer;
|
||||
};
|
||||
|
||||
struct parser_helper_file_t {
|
||||
|
||||
/**
|
||||
* File name
|
||||
*/
|
||||
char *name;
|
||||
|
||||
/**
|
||||
* File stream
|
||||
*/
|
||||
FILE *file;
|
||||
|
||||
/**
|
||||
* Enumerator of paths matching the most recent inclusion pattern.
|
||||
*/
|
||||
enumerator_t *matches;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the given file data.
|
||||
*/
|
||||
static void parser_helper_file_destroy(parser_helper_file_t *this)
|
||||
{
|
||||
if (this->file)
|
||||
{
|
||||
fclose(this->file);
|
||||
}
|
||||
free(this->name);
|
||||
DESTROY_IF(this->matches);
|
||||
free(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current file, if any.
|
||||
*/
|
||||
static parser_helper_file_t *current_file(private_parser_helper_t *this)
|
||||
{
|
||||
parser_helper_file_t *file;
|
||||
|
||||
array_get(this->files, ARRAY_TAIL, &file);
|
||||
if (file->name)
|
||||
{
|
||||
return file;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
METHOD(parser_helper_t, file_next, FILE*,
|
||||
private_parser_helper_t *this)
|
||||
{
|
||||
parser_helper_file_t *file, *next;
|
||||
char *name;
|
||||
|
||||
array_get(this->files, ARRAY_TAIL, &file);
|
||||
if (!file->matches)
|
||||
{
|
||||
array_remove(this->files, ARRAY_TAIL, NULL);
|
||||
parser_helper_file_destroy(file);
|
||||
/* continue with previous includes, if any */
|
||||
array_get(this->files, ARRAY_TAIL, &file);
|
||||
}
|
||||
if (file->matches)
|
||||
{
|
||||
while (file->matches->enumerate(file->matches, &name, NULL))
|
||||
{
|
||||
INIT(next,
|
||||
.name = strdup(name),
|
||||
.file = fopen(name, "r"),
|
||||
);
|
||||
|
||||
if (next->file)
|
||||
{
|
||||
array_insert(this->files, ARRAY_TAIL, next);
|
||||
return next->file;
|
||||
}
|
||||
PARSER_DBG2(&this->public, "unable to open '%s'", name);
|
||||
parser_helper_file_destroy(next);
|
||||
}
|
||||
file->matches->destroy(file->matches);
|
||||
file->matches = NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
METHOD(parser_helper_t, file_include, void,
|
||||
private_parser_helper_t *this, char *pattern)
|
||||
{
|
||||
parser_helper_file_t *file;
|
||||
char pat[PATH_MAX];
|
||||
|
||||
array_get(this->files, ARRAY_TAIL, &file);
|
||||
if (!pattern || !*pattern)
|
||||
{
|
||||
PARSER_DBG1(&this->public, "no include pattern specified, ignored");
|
||||
file->matches = enumerator_create_empty();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file->name || pattern[0] == '/')
|
||||
{ /* absolute path */
|
||||
if (snprintf(pat, sizeof(pat), "%s", pattern) >= sizeof(pat))
|
||||
{
|
||||
PARSER_DBG1(&this->public, "include pattern too long, ignored");
|
||||
file->matches = enumerator_create_empty();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{ /* base relative paths to the directory of the current file */
|
||||
char *dir = path_dirname(file->name);
|
||||
if (snprintf(pat, sizeof(pat), "%s/%s", dir, pattern) >= sizeof(pat))
|
||||
{
|
||||
PARSER_DBG1(&this->public, "include pattern too long, ignored");
|
||||
free(dir);
|
||||
file->matches = enumerator_create_empty();
|
||||
return;
|
||||
}
|
||||
free(dir);
|
||||
}
|
||||
|
||||
file->matches = enumerator_create_glob(pat);
|
||||
if (!file->matches)
|
||||
{ /* if glob(3) is not available, try to load pattern directly */
|
||||
file->matches = enumerator_create_single(strdup(pat), free);
|
||||
}
|
||||
}
|
||||
|
||||
METHOD(parser_helper_t, string_init, void,
|
||||
private_parser_helper_t *this)
|
||||
{
|
||||
chunk_t data;
|
||||
|
||||
data = this->writer->extract_buf(this->writer);
|
||||
chunk_free(&data);
|
||||
}
|
||||
|
||||
METHOD(parser_helper_t, string_add, void,
|
||||
private_parser_helper_t *this, char *str)
|
||||
{
|
||||
this->writer->write_data(this->writer, chunk_from_str(str));
|
||||
}
|
||||
|
||||
METHOD(parser_helper_t, string_get, char*,
|
||||
private_parser_helper_t *this)
|
||||
{
|
||||
chunk_t data;
|
||||
|
||||
this->writer->write_data(this->writer, chunk_from_chars('\0'));
|
||||
data = this->writer->extract_buf(this->writer);
|
||||
return data.ptr;
|
||||
}
|
||||
|
||||
METHOD(parser_helper_t, destroy, void,
|
||||
private_parser_helper_t *this)
|
||||
{
|
||||
array_destroy_function(this->files, (void*)parser_helper_file_destroy, NULL);
|
||||
this->writer->destroy(this->writer);
|
||||
free(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Described in header
|
||||
*/
|
||||
void parser_helper_log(int level, parser_helper_t *ctx, char *fmt, ...)
|
||||
{
|
||||
private_parser_helper_t *this = (private_parser_helper_t*)ctx;
|
||||
parser_helper_file_t *file;
|
||||
char msg[8192];
|
||||
va_list args;
|
||||
int line;
|
||||
|
||||
va_start(args, fmt);
|
||||
vsnprintf(msg, sizeof(msg), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
file = current_file(this);
|
||||
line = ctx->get_lineno ? ctx->get_lineno(ctx->scanner) : 0;
|
||||
if (file)
|
||||
{
|
||||
dbg(DBG_CFG, level, "%s:%d: %s", file->name, line, msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg(DBG_CFG, level, "%s", msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Described in header
|
||||
*/
|
||||
parser_helper_t *parser_helper_create(void *context)
|
||||
{
|
||||
private_parser_helper_t *this;
|
||||
parser_helper_file_t *sentinel;
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.context = context,
|
||||
.file_include = _file_include,
|
||||
.file_next = _file_next,
|
||||
.string_init = _string_init,
|
||||
.string_add = _string_add,
|
||||
.string_get = _string_get,
|
||||
.destroy = _destroy,
|
||||
},
|
||||
.files = array_create(0, 0),
|
||||
.writer = bio_writer_create(0),
|
||||
);
|
||||
|
||||
INIT(sentinel,
|
||||
.name = NULL,
|
||||
);
|
||||
array_insert(this->files, ARRAY_TAIL, sentinel);
|
||||
|
||||
return &this->public;
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Tobias Brunner
|
||||
* Hochschule fuer Technik Rapperswil
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup parser_helper parser_helper
|
||||
* @{ @ingroup utils
|
||||
*/
|
||||
|
||||
#ifndef PARSER_HELPER_H_
|
||||
#define PARSER_HELPER_H_
|
||||
|
||||
#include <utils/debug.h>
|
||||
#include <collections/array.h>
|
||||
#include <bio/bio_writer.h>
|
||||
|
||||
typedef struct parser_helper_t parser_helper_t;
|
||||
|
||||
/**
|
||||
* Helper class for flex/bison based parsers.
|
||||
*
|
||||
* <code>PREFIX</code> equals whatever is configure with
|
||||
* <code>%option prefix</code> resp. <code>%name-prefix</code>.
|
||||
*/
|
||||
struct parser_helper_t {
|
||||
|
||||
/**
|
||||
* A user defined parser context object.
|
||||
*/
|
||||
const void *context;
|
||||
|
||||
/**
|
||||
* Opaque object allocated by the lexer, should be set with:
|
||||
* @code
|
||||
* PREFIXlex_init_extra(helper, &helper->scanner).
|
||||
* @endcode
|
||||
*/
|
||||
void *scanner;
|
||||
|
||||
/**
|
||||
* Function to determine the current line number (defined by the lexer).
|
||||
*
|
||||
* Basically, this should be assigned to <code>PREFIXget_lineno</code>.
|
||||
*
|
||||
* @param scanner the lexer
|
||||
* @return current line number
|
||||
*/
|
||||
int (*get_lineno)(void *scanner);
|
||||
|
||||
/**
|
||||
* Resolves the given include pattern, relative to the location of the
|
||||
* current file.
|
||||
*
|
||||
* Call file_next() to open the next file.
|
||||
*
|
||||
* @param pattern file pattern
|
||||
*/
|
||||
void (*file_include)(parser_helper_t *this, char *pattern);
|
||||
|
||||
/**
|
||||
* Get the next file to process.
|
||||
*
|
||||
* This will return NULL if all files matching the most recent pattern
|
||||
* have been handled. If there are other patterns the next call will then
|
||||
* return the next file matching the previous pattern.
|
||||
*
|
||||
* When hitting <code>\<\<EOF\>\></code> first call
|
||||
* @code
|
||||
* PREFIXpop_buffer_state(yyscanner);
|
||||
* @endcode
|
||||
* then call this method to check if there are more files to include for
|
||||
* the most recent call to file_include(), if so, call
|
||||
* @code
|
||||
* PREFIXset_in(file, helper->scanner);
|
||||
* PREFIXpush_buffer_state(PREFIX_create_buffer(file, YY_BUF_SIZE,
|
||||
* helper->scanner), helper->scanner);
|
||||
* @endcode
|
||||
*
|
||||
* If there are no more files to process check
|
||||
* <code>YY_CURRENT_BUFFER</code> and if it is FALSE call yyterminate().
|
||||
*
|
||||
* @return next file to process, or NULL (see comment)
|
||||
*/
|
||||
FILE *(*file_next)(parser_helper_t *this);
|
||||
|
||||
/**
|
||||
* Start parsing a string, discards any currently stored data.
|
||||
*/
|
||||
void (*string_init)(parser_helper_t *this);
|
||||
|
||||
/**
|
||||
* Append the given string.
|
||||
*
|
||||
* @param str string to append
|
||||
*/
|
||||
void (*string_add)(parser_helper_t *this, char *str);
|
||||
|
||||
/**
|
||||
* Extract the current string buffer as null-terminated string. Can only
|
||||
* be called once per string.
|
||||
*
|
||||
* @return allocated string
|
||||
*/
|
||||
char *(*string_get)(parser_helper_t *this);
|
||||
|
||||
/**
|
||||
* Destroy this instance.
|
||||
*/
|
||||
void (*destroy)(parser_helper_t *this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Log the given message either as error or warning
|
||||
*
|
||||
* @param level log level
|
||||
* @param ctx current parser context
|
||||
* @param fmt error message format
|
||||
* @param ... additional arguments
|
||||
*/
|
||||
void parser_helper_log(int level, parser_helper_t *ctx, char *fmt, ...);
|
||||
|
||||
#if DEBUG_LEVEL >= 1
|
||||
# define PARSER_DBG1(ctx, fmt, ...) parser_helper_log(1, ctx, fmt, ##__VA_ARGS__)
|
||||
#endif
|
||||
#if DEBUG_LEVEL >= 2
|
||||
# define PARSER_DBG2(ctx, fmt, ...) parser_helper_log(2, ctx, fmt, ##__VA_ARGS__)
|
||||
#endif
|
||||
#if DEBUG_LEVEL >= 3
|
||||
# define PARSER_DBG3(ctx, fmt, ...) parser_helper_log(3, ctx, fmt, ##__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#ifndef PARSER_DBG1
|
||||
# define PARSER_DBG1(...) {}
|
||||
#endif
|
||||
#ifndef PARSER_DBG2
|
||||
# define PARSER_DBG2(...) {}
|
||||
#endif
|
||||
#ifndef PARSER_DBG3
|
||||
# define PARSER_DBG3(...) {}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Create a parser helper object
|
||||
*
|
||||
* @param context user defined parser context
|
||||
* @return parser helper
|
||||
*/
|
||||
parser_helper_t *parser_helper_create(void *context);
|
||||
|
||||
#endif /** PARSER_HELPER_H_ @}*/
|
||||
Reference in New Issue
Block a user