strongswan/src/libstrongswan/settings.c

1221 lines
24 KiB
C

/*
* Copyright (C) 2010 Tobias Brunner
* Copyright (C) 2008 Martin Willi
* 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
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <errno.h>
#include <limits.h>
#include <libgen.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#ifdef HAVE_GLOB_H
#include <glob.h>
#endif /* HAVE_GLOB_H */
#include "settings.h"
#include "debug.h"
#include "utils/linked_list.h"
#include "threading/rwlock.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
*/
struct private_settings_t {
/**
* public functions
*/
settings_t public;
/**
* top level section
*/
section_t *top;
/**
* contents of loaded files and in-memory settings (char*)
*/
linked_list_t *contents;
/**
* 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;
/**
* subsections, as section_t
*/
linked_list_t *sections;
/**
* key value pairs, as kv_t
*/
linked_list_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),
.sections = linked_list_create(),
.kv = linked_list_create(),
);
return this;
}
/**
* destroy a section
*/
static void section_destroy(section_t *this)
{
this->kv->destroy_function(this->kv, (void*)kv_destroy);
this->sections->destroy_function(this->sections, (void*)section_destroy);
free(this->name);
free(this);
}
/**
* Purge contents of a section
*/
static void section_purge(section_t *this)
{
this->kv->destroy_function(this->kv, (void*)kv_destroy);
this->kv = linked_list_create();
this->sections->destroy_function(this->sections, (void*)section_destroy);
this->sections = linked_list_create();
}
/**
* callback to find a section by name
*/
static bool section_find(section_t *this, char *name)
{
return streq(this->name, name);
}
/**
* callback to find a kv pair by key
*/
static bool kv_find(kv_t *this, char *key)
{
return streq(this->key, key);
}
/**
* Print a format key, but consume already processed arguments
*/
static bool print_key(char *buf, int len, char *start, char *key, va_list args)
{
va_list copy;
bool res;
char *pos;
va_copy(copy, args);
while (start < key)
{
pos = strchr(start, '%');
if (!pos)
{
start += strlen(start) + 1;
continue;
}
pos++;
switch (*pos)
{
case 'd':
va_arg(copy, int);
break;
case 's':
va_arg(copy, char*);
break;
case 'N':
va_arg(copy, enum_name_t*);
va_arg(copy, int);
break;
case '%':
break;
default:
DBG1(DBG_CFG, "settings with %%%c not supported!", *pos);
break;
}
start = pos;
if (*start)
{
start++;
}
}
res = vsnprintf(buf, len, key, copy) < len;
va_end(copy);
return res;
}
/**
* Find a section by a given key, using buffered key, reusable buffer.
* If "ensure" is TRUE, the sections are created if they don't exist.
*/
static section_t *find_section_buffered(section_t *section,
char *start, char *key, va_list args, char *buf, int len,
bool ensure)
{
char *pos;
section_t *found = NULL;
if (section == NULL)
{
return NULL;
}
pos = strchr(key, '.');
if (pos)
{
*pos = '\0';
pos++;
}
if (!print_key(buf, len, start, key, args))
{
return NULL;
}
if (section->sections->find_first(section->sections,
(linked_list_match_t)section_find,
(void**)&found, buf) != SUCCESS)
{
if (ensure)
{
found = section_create(buf);
section->sections->insert_last(section->sections, found);
}
}
if (found && pos)
{
return find_section_buffered(found, start, pos, args, buf, len, ensure);
}
return found;
}
/**
* Find a section by a given key (thread-safe).
*/
static section_t *find_section(private_settings_t *this, section_t *section,
char *key, va_list args)
{
char buf[128], keybuf[512];
section_t *found;
if (snprintf(keybuf, sizeof(keybuf), "%s", key) >= sizeof(keybuf))
{
return NULL;
}
this->lock->read_lock(this->lock);
found = find_section_buffered(section, keybuf, keybuf, args, buf,
sizeof(buf), FALSE);
this->lock->unlock(this->lock);
return found;
}
/**
* Ensure that the section with the given key exists (thread-safe).
*/
static section_t *ensure_section(private_settings_t *this, section_t *section,
char *key, va_list args)
{
char buf[128], keybuf[512];
section_t *found;
if (snprintf(keybuf, sizeof(keybuf), "%s", key) >= sizeof(keybuf))
{
return NULL;
}
/* we might have to change the tree */
this->lock->write_lock(this->lock);
found = find_section_buffered(section, keybuf, keybuf, args, buf,
sizeof(buf), TRUE);
this->lock->unlock(this->lock);
return found;
}
/**
* Find the key/value pair for a key, using buffered key, reusable buffer
* If "ensure" is TRUE, the sections (and key/value pair) are created if they
* don't exist.
*/
static kv_t *find_value_buffered(section_t *section, char *start, char *key,
va_list args, char *buf, int len, bool ensure)
{
char *pos;
kv_t *kv = NULL;
section_t *found = NULL;
if (section == NULL)
{
return NULL;
}
pos = strchr(key, '.');
if (pos)
{
*pos = '\0';
pos++;
if (!print_key(buf, len, start, key, args))
{
return NULL;
}
if (section->sections->find_first(section->sections,
(linked_list_match_t)section_find,
(void**)&found, buf) != SUCCESS)
{
if (!ensure)
{
return NULL;
}
found = section_create(buf);
section->sections->insert_last(section->sections, found);
}
return find_value_buffered(found, start, pos, args, buf, len,
ensure);
}
else
{
if (!print_key(buf, len, start, key, args))
{
return NULL;
}
if (section->kv->find_first(section->kv, (linked_list_match_t)kv_find,
(void**)&kv, buf) != SUCCESS)
{
if (ensure)
{
kv = kv_create(buf, NULL);
section->kv->insert_last(section->kv, kv);
}
}
}
return kv;
}
/**
* Find the string value for a key (thread-safe).
*/
static char *find_value(private_settings_t *this, section_t *section,
char *key, va_list args)
{
char buf[128], keybuf[512], *value = NULL;
kv_t *kv;
if (snprintf(keybuf, sizeof(keybuf), "%s", key) >= sizeof(keybuf))
{
return NULL;
}
this->lock->read_lock(this->lock);
kv = find_value_buffered(section, keybuf, keybuf, args, buf, sizeof(buf),
FALSE);
if (kv)
{
value = kv->value;
}
this->lock->unlock(this->lock);
return value;
}
/**
* Set a value to a copy of the given string (thread-safe).
*/
static void set_value(private_settings_t *this, section_t *section,
char *key, va_list args, char *value)
{
char buf[128], keybuf[512];
kv_t *kv;
if (snprintf(keybuf, sizeof(keybuf), "%s", key) >= sizeof(keybuf))
{
return;
}
this->lock->write_lock(this->lock);
kv = find_value_buffered(section, keybuf, keybuf, args, buf, sizeof(buf),
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);
}
}
this->lock->unlock(this->lock);
}
METHOD(settings_t, get_str, char*,
private_settings_t *this, char *key, char *def, ...)
{
char *value;
va_list args;
va_start(args, def);
value = find_value(this, this->top, key, args);
va_end(args);
if (value)
{
return value;
}
return def;
}
/**
* Described in header
*/
inline bool settings_value_as_bool(char *value, bool def)
{
if (value)
{
if (strcaseeq(value, "1") ||
strcaseeq(value, "yes") ||
strcaseeq(value, "true") ||
strcaseeq(value, "enabled"))
{
return TRUE;
}
else if (strcaseeq(value, "0") ||
strcaseeq(value, "no") ||
strcaseeq(value, "false") ||
strcaseeq(value, "disabled"))
{
return FALSE;
}
}
return def;
}
METHOD(settings_t, get_bool, bool,
private_settings_t *this, char *key, bool def, ...)
{
char *value;
va_list args;
va_start(args, def);
value = find_value(this, this->top, key, args);
va_end(args);
return settings_value_as_bool(value, def);
}
/**
* Described in header
*/
inline int settings_value_as_int(char *value, int def)
{
int intval;
if (value)
{
errno = 0;
intval = strtol(value, NULL, 10);
if (errno == 0)
{
return intval;
}
}
return def;
}
METHOD(settings_t, get_int, int,
private_settings_t *this, char *key, int def, ...)
{
char *value;
va_list args;
va_start(args, def);
value = find_value(this, this->top, key, args);
va_end(args);
return settings_value_as_int(value, def);
}
/**
* Described in header
*/
inline double settings_value_as_double(char *value, double def)
{
double dval;
if (value)
{
errno = 0;
dval = strtod(value, NULL);
if (errno == 0)
{
return dval;
}
}
return def;
}
METHOD(settings_t, get_double, double,
private_settings_t *this, char *key, double def, ...)
{
char *value;
va_list args;
va_start(args, def);
value = find_value(this, this->top, key, args);
va_end(args);
return settings_value_as_double(value, def);
}
/**
* Described in header
*/
inline u_int32_t settings_value_as_time(char *value, u_int32_t def)
{
char *endptr;
u_int32_t timeval;
if (value)
{
errno = 0;
timeval = strtoul(value, &endptr, 10);
if (errno == 0)
{
switch (*endptr)
{
case 'd': /* time in days */
timeval *= 24 * 3600;
break;
case 'h': /* time in hours */
timeval *= 3600;
break;
case 'm': /* time in minutes */
timeval *= 60;
break;
case 's': /* time in seconds */
default:
break;
}
return timeval;
}
}
return def;
}
METHOD(settings_t, get_time, u_int32_t,
private_settings_t *this, char *key, u_int32_t def, ...)
{
char *value;
va_list args;
va_start(args, def);
value = find_value(this, this->top, key, args);
va_end(args);
return settings_value_as_time(value, def);
}
METHOD(settings_t, set_str, void,
private_settings_t *this, char *key, char *value, ...)
{
va_list args;
va_start(args, value);
set_value(this, this->top, key, args, value);
va_end(args);
}
METHOD(settings_t, set_bool, void,
private_settings_t *this, char *key, bool value, ...)
{
va_list args;
va_start(args, value);
set_value(this, this->top, key, args, value ? "1" : "0");
va_end(args);
}
METHOD(settings_t, set_int, void,
private_settings_t *this, char *key, int value, ...)
{
char val[16];
va_list args;
va_start(args, value);
if (snprintf(val, sizeof(val), "%d", value) < sizeof(val))
{
set_value(this, this->top, key, args, val);
}
va_end(args);
}
METHOD(settings_t, set_double, void,
private_settings_t *this, char *key, double value, ...)
{
char val[64];
va_list args;
va_start(args, value);
if (snprintf(val, sizeof(val), "%f", value) < sizeof(val))
{
set_value(this, this->top, key, args, val);
}
va_end(args);
}
METHOD(settings_t, set_time, void,
private_settings_t *this, char *key, u_int32_t value, ...)
{
char val[16];
va_list args;
va_start(args, value);
if (snprintf(val, sizeof(val), "%u", value) < sizeof(val))
{
set_value(this, this->top, key, args, val);
}
va_end(args);
}
/**
* Enumerate section names, not sections
*/
static bool section_filter(void *null, section_t **in, char **out)
{
*out = (*in)->name;
return TRUE;
}
METHOD(settings_t, create_section_enumerator, enumerator_t*,
private_settings_t *this, char *key, ...)
{
section_t *section;
va_list args;
va_start(args, key);
section = find_section(this, this->top, key, args);
va_end(args);
if (!section)
{
return enumerator_create_empty();
}
this->lock->read_lock(this->lock);
return enumerator_create_filter(
section->sections->create_enumerator(section->sections),
(void*)section_filter, this->lock, (void*)this->lock->unlock);
}
/**
* Enumerate key and values, not kv_t entries
*/
static bool kv_filter(void *null, kv_t **in, char **key,
void *none, char **value)
{
*key = (*in)->key;
*value = (*in)->value;
return TRUE;
}
METHOD(settings_t, create_key_value_enumerator, enumerator_t*,
private_settings_t *this, char *key, ...)
{
section_t *section;
va_list args;
va_start(args, key);
section = find_section(this, this->top, key, args);
va_end(args);
if (!section)
{
return enumerator_create_empty();
}
this->lock->read_lock(this->lock);
return enumerator_create_filter(
section->kv->create_enumerator(section->kv),
(void*)kv_filter, this->lock, (void*)this->lock->unlock);
}
/**
* 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 (section->sections->find_first(section->sections,
(linked_list_match_t)section_find,
(void**)&sub, key) != SUCCESS)
{
sub = section_create(key);
if (parse_section(contents, file, level, &inner, sub))
{
section->sections->insert_last(section->sections,
sub);
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 (section->kv->find_first(section->kv,
(linked_list_match_t)kv_find,
(void**)&kv, key) != SUCCESS)
{
kv = kv_create(key, value);
section->kv->insert_last(section->kv, kv);
}
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)
{
DBG2(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 + 1);
text[len] = '\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))
{
DBG2(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 = strdup(file);
dir = dirname(dir);
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)
{
DBG2(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 = extension->sections->create_enumerator(extension->sections);
while (enumerator->enumerate(enumerator, (void**)&sec))
{
section_t *found;
if (base->sections->find_first(base->sections,
(linked_list_match_t)section_find, (void**)&found,
sec->name) == SUCCESS)
{
section_extend(found, sec);
}
else
{
extension->sections->remove_at(extension->sections, enumerator);
base->sections->insert_last(base->sections, sec);
}
}
enumerator->destroy(enumerator);
enumerator = extension->kv->create_enumerator(extension->kv);
while (enumerator->enumerate(enumerator, (void**)&kv))
{
kv_t *found;
if (base->kv->find_first(base->kv, (linked_list_match_t)kv_find,
(void**)&found, kv->key) == SUCCESS)
{
found->value = kv->value;
}
else
{
extension->kv->remove_at(extension->kv, enumerator);
base->kv->insert_last(base->kv, kv);
}
}
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.
*/
static bool load_files_internal(private_settings_t *this, section_t *parent,
char *pattern, bool merge)
{
char *text;
linked_list_t *contents = linked_list_create();
section_t *section = section_create(NULL);
if (pattern == NULL)
{
pattern = STRONGSWAN_CONF;
}
if (!parse_files(contents, NULL, 0, pattern, section))
{
contents->destroy_function(contents, (void*)free);
section_destroy(section);
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);
}
this->lock->unlock(this->lock);
section_destroy(section);
contents->destroy(contents);
return TRUE;
}
METHOD(settings_t, load_files, bool,
private_settings_t *this, char *pattern, bool merge)
{
return load_files_internal(this, this->top, pattern, merge);
}
METHOD(settings_t, load_files_section, bool,
private_settings_t *this, char *pattern, bool merge, char *key, ...)
{
section_t *section;
va_list args;
va_start(args, key);
section = ensure_section(this, this->top, key, args);
va_end(args);
if (!section)
{
return FALSE;
}
return load_files_internal(this, section, pattern, merge);
}
METHOD(settings_t, destroy, void,
private_settings_t *this)
{
section_destroy(this->top);
this->contents->destroy_function(this->contents, (void*)free);
this->lock->destroy(this->lock);
free(this);
}
/*
* see header file
*/
settings_t *settings_create(char *file)
{
private_settings_t *this;
INIT(this,
.public = {
.get_str = _get_str,
.get_int = _get_int,
.get_double = _get_double,
.get_time = _get_time,
.get_bool = _get_bool,
.set_str = _set_str,
.set_int = _set_int,
.set_double = _set_double,
.set_time = _set_time,
.set_bool = _set_bool,
.create_section_enumerator = _create_section_enumerator,
.create_key_value_enumerator = _create_key_value_enumerator,
.load_files = _load_files,
.load_files_section = _load_files_section,
.destroy = _destroy,
},
.top = section_create(NULL),
.contents = linked_list_create(),
.lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
);
load_files(this, file, FALSE);
return &this->public;
}