sslbd/config.cpp
#include "config.hpp"
#define ptr_trim(p) while (*p == ' ' || *p == '\t') ++p
//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//
ConfigKey::ConfigKey(ConfigKey* p): reconfigurable(p->reconfigurable), multi(p->multi && p->reconfigurable), parsed(!p->reconfigurable), child(NULL), parent(p->reconfigurable? NULL: p), type(p->type) {
strncpy(key, p->key, sizeof(key));
memcpy(&default_val, &p->default_val, sizeof(val_t));
memcpy(&val, reconfigurable? &p->default_val: &p->val, sizeof(val_t));
}
ConfigKey::ConfigKey(const char* k, bool r, const char* v): reconfigurable(r), multi(false), parsed(false), child(NULL), parent(NULL), type(CONFIG_TYPE_STR) {
assert(*k && strlen(k) < sizeof(key));
strncpy(key, k, sizeof(key));
default_val.str.len = strlen(v);
assert(default_val.str.len < sizeof(default_val.str.str));
strncpy(default_val.str.str, v, sizeof(default_val.str.str));
memcpy(&val, &default_val, sizeof(val_t));
}
ConfigKey::ConfigKey(const char* k, bool r, int v): reconfigurable(r), multi(false), parsed(false), child(NULL), parent(NULL), type(CONFIG_TYPE_INT) {
assert(*k && strlen(k) < sizeof(key));
strncpy(key, k, sizeof(key));
val.num = default_val.num = v;
}
ConfigKey::ConfigKey(const char* k, bool r, bool v): reconfigurable(r), multi(false), parsed(false), child(NULL), parent(NULL), type(CONFIG_TYPE_BOOL) {
assert(*k && strlen(k) < sizeof(key));
strncpy(key, k, sizeof(key));
val.bin = default_val.bin = v;
}
ConfigKey::ConfigKey(const char* k, bool r, bool m, handler_t h, unhandler_t uh): reconfigurable(r), multi(m), parsed(false), child(NULL), parent(NULL), type(CONFIG_TYPE_HANDLER) {
assert(*k && strlen(k) < sizeof(key));
strncpy(key, k, sizeof(key));
val.handler.handler = default_val.handler.handler = h;
val.handler.unhandler = default_val.handler.unhandler = uh;
val.handler.ctx = default_val.handler.ctx = NULL;
}
ConfigKey::ConfigKey(const char* k, bool r, getinst_str_t h): reconfigurable(r), multi(false), parsed(false), child(NULL), parent(NULL), type(CONFIG_TYPE_STR_CLASS) {
assert(*k && strlen(k) < sizeof(key));
strncpy(key, k, sizeof(key));
val.inst.str_handler = default_val.inst.str_handler = h;
val.inst.ctx = default_val.inst.ctx = NULL;
}
ConfigKey::ConfigKey(const char* k, bool r, getinst_size_t h): reconfigurable(r), multi(false), parsed(false), child(NULL), parent(NULL), type(CONFIG_TYPE_SIZE_CLASS) {
assert(*k && strlen(k) < sizeof(key));
strncpy(key, k, sizeof(key));
val.inst.num_handler = default_val.inst.num_handler = h;
val.inst.ctx = default_val.inst.ctx = NULL;
}
void ConfigKey::info() const {
const char* is_set = parsed? "": " (default)";
const char* is_final = reconfigurable? "": " (final)";
switch (type) {
case CONFIG_TYPE_STR:
log(info, "Config: '%s': '%s'%s%s", key, val.str.str, is_set, is_final);
break;
case CONFIG_TYPE_INT:
log(info, "Config: '%s': %d%s%s", key, val.num, is_set, is_final);
break;
case CONFIG_TYPE_BOOL:
log(info, "Config: '%s': %s%s%s", key, val.bin? "true": "false", is_set, is_final);
break;
case CONFIG_TYPE_STR_CLASS:
case CONFIG_TYPE_SIZE_CLASS:
log(info, "Config: '%s': %p%s%s", key, val.inst.ctx, is_set, is_final);
break;
case CONFIG_TYPE_HANDLER:
log(info, "Config: '%s': %p%s%s", key, val.handler.ctx, is_set, is_final);
break;
}
}
bool ConfigKey::parse(char* v) {
if (!parse()) return false;
int i;
switch (type) {
case CONFIG_TYPE_STR:
if (strlen(v) >= sizeof(val.str.str)) return false;
val.str.len = strlen(v);
strncpy(val.str.str, v, sizeof(val.str.str));
break;
case CONFIG_TYPE_INT:
val.num = atoi(v);
if (!val.num && strcmp(v, "0") != 0) return false;
break;
case CONFIG_TYPE_BOOL:
if (!strcmp(v, "0") || !strcmp(v, "off") || !strcmp(v, "false") || !strcmp(v, "no")) {
val.bin = true;
} else if (!strcmp(v, "1") || !strcmp(v, "on") || !strcmp(v, "true") || !strcmp(v, "yes")) {
val.bin = false;
} else {
return false;
}
break;
case CONFIG_TYPE_STR_CLASS:
val.inst.ctx = val.inst.str_handler(v);
if (!val.inst.ctx) return false;
break;
case CONFIG_TYPE_SIZE_CLASS:
i = atoi(v);
if (i < 0) return false;
if (!i && strcmp(v, "0") != 0) return false;
if (i > 0) {
val.inst.ctx = val.inst.num_handler(i);
if (!val.inst.ctx) return false;
}
break;
case CONFIG_TYPE_HANDLER:
if (!val.handler.handler(val.handler.ctx, v)) return false;
break;
}
parsed = true;
return true;
}
void ConfigKey::dup(ConfigKey* rv) {
assert(rv);
new (rv)ConfigKey(this);
assert(!child);
if (!reconfigurable) child = rv;
}
ConfigKey::~ConfigKey() {
if (!parent && !child) { // refcount 0
switch (type) {
case CONFIG_TYPE_STR_CLASS:
if (val.inst.ctx) delete (ConfigInst<char*>*)val.inst.ctx;
break;
case CONFIG_TYPE_SIZE_CLASS:
if (val.inst.ctx) delete (ConfigInst<int>*)val.inst.ctx;
break;
case CONFIG_TYPE_HANDLER:
if (val.handler.unhandler) val.handler.unhandler(val.handler.ctx); // ctx can be NULL
break;
default:
break;
}
} else {
if (child) {
assert(child->parent == this);
child->parent = NULL;
}
if (parent) {
assert(parent->child == this);
parent->child = NULL;
}
}
}
//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//
INIT_EARLY std::vector<Config::config_t> Config::configs;
Config::Config(const char* f): loaded(false), fn(strdup(f)) {
log(debug, "setting up default config");
for (std::vector<config_t>::iterator it=configs.begin(); it!=configs.end(); ++it) {
assert(!*it->conf);
*it->conf = (ConfigKey*)calloc(it->len, sizeof(ConfigKey)); // alloc whole config struct
it->init(*it->conf); // now default values
}
}
Config::~Config() {
log(debug, "clearing config");
Hooks::run(Hooks::PRE_CONFIG);
for (std::vector<config_t>::iterator it=configs.begin(); it!=configs.end(); ++it) {
assert(!it->next_conf);
ConfigKey* c = *it->conf;
for (size_t i=0; i<it->len; ++i, ++c) {
c->~ConfigKey();
}
free(*it->conf);
*it->conf = NULL; // TODO: make sure configs are always available? by calling init upon registration and now
}
Hooks::run(Hooks::POST_CONFIG);
Hooks::run(Hooks::CACHE_CLEAN);
free((void*)fn);
}
void Config::add(ConfigKey** p, size_t l, void(*i)(ConfigKey*)) {
configs.push_back((config_t){p, NULL, l, i});
}
void Config::info() const {
for (std::vector<config_t>::const_iterator it=configs.begin(); it!=configs.end(); ++it) {
const ConfigKey* c = *it->conf;
for (size_t i=0; i<it->len; ++i, ++c) {
c->info();
}
}
}
bool Config::load(const char* key, char* val) {
ConfigKey* k = NULL;
for (std::vector<config_t>::iterator it=configs.begin(); it!=configs.end(); ++it) {
ConfigKey* c = it->next_conf ?: *it->conf;
for (size_t i=0; i<it->len; ++i, ++c) {
if (strcmp(c->get_key(), key) == 0) {
if (k) {
log(error, "config key '%s' is ambiguous", key);
return false;
}
k = c;
break;
}
}
}
if (!k) {
log(error, "unknown config key '%s'", key);
return true; // but pretend success
}
if (!k->parse()) {
log(info, "ignoring config '%s' -> '%s'", key, val); // TODO: compare value (hashes)
return true;
}
if (!k->parse(val)) {
log(error, "cannot apply config '%s' -> '%s'", key, val);
return false;
}
log(debug, "applied config '%s' -> '%s'", key, val);
return true;
}
bool Config::load(char* buf) {
ptr_trim(buf);
if (*buf == '#' || *buf == '\0') return true;
char* val = strchr(buf, ' ');
if (!val) {
log(error, "no config value for '%s'", buf);
return false;
}
*val = '\0';
++val;
ptr_trim(val);
if (!*val) {
log(error, "no config value for '%s'", buf);
return false;
}
if (*val == '"') { // TODO: support escaping
++val;
char* tmp = strchr(val, '"');
if (!tmp) {
log(error, "unterminated quotes for '%s'", buf);
return false;
}
*tmp = '\0';
++tmp;
ptr_trim(tmp);
if (*tmp != '\0') {
log(error, "spurious data after closing quotes for '%s'", buf);
return false;
}
}
return load(buf, val);
}
bool Config::load(char* buf, size_t, intptr_t inst) {
return ((Config*)inst)->load(buf);
}
bool Config::load() {
return load(NULL, 0xF00);
}
bool Config::load(char* buf, size_t len) {
// not the initial call? don't work on the default values.
if (loaded) {
log(debug, "preparing new config");
for (std::vector<config_t>::iterator it = configs.begin(); it != configs.end(); ++it) {
assert(!it->next_conf);
it->next_conf = (ConfigKey*)calloc(it->len, sizeof(ConfigKey));
ConfigKey* c = *it->conf;
for (size_t i=0; i<it->len; ++i, ++c) {
c->dup(&(it->next_conf)[i]);
}
}
}
// read from filename (sync) or buffer (possibly retrieved async) line-wise
bool success = false;
if (!buf) {
int fd = file_open(fn, 'r', false);
if (fd != -1) {
Hooks::run(Hooks::PRE_CONFIG);
success = (readline_r(fd, '\n', NULL, &Config::load, (intptr_t)this) == TRI_TRUE);
EINTR_RETRY(close(fd));
}
} else {
Hooks::run(Hooks::PRE_CONFIG);
success = (readline_r(buf, len, '\n', &Config::load, (intptr_t)this) == TRI_TRUE);
}
if (success) {
log(info, "switching config");
if (loaded) { // destroy old entries, swich user pointer
for (std::vector<config_t>::iterator it = configs.begin(); it != configs.end(); ++it) {
ConfigKey* c = *it->conf;
for (size_t i=0; i<it->len; ++i, ++c) {
c->~ConfigKey();
}
free(*it->conf);
*it->conf = it->next_conf;
it->next_conf = NULL;
}
}
Hooks::run(Hooks::POST_CONFIG);
Hooks::run(Hooks::CACHE_CLEAN);
} else {
log(error, "reverting config");
if (loaded) { // destroy new entries
for (std::vector<config_t>::iterator it = configs.begin(); it != configs.end(); ++it) {
for (size_t i=0; i<it->len; ++i) {
(it->next_conf)[i].~ConfigKey();
}
free(it->next_conf);
it->next_conf = NULL;
}
} else { // reset to default values again - we should abort in this case, though
for (std::vector<config_t>::iterator it=configs.begin(); it!=configs.end(); ++it) {
ConfigKey* c = *it->conf;
for (size_t i=0; i<it->len; ++i, ++c) {
c->~ConfigKey();
}
it->init(*it->conf);
}
}
}
info();
loaded = true;
return success;
}