mirror of
https://github.com/servalproject/serval-dna.git
synced 2024-12-23 15:02:27 +00:00
0e435683f7
Move alloca_sockaddr() to strbuf_helpers.h So that stand-alone executables can use them without pulling in the entire logging framework.
566 lines
16 KiB
C
566 lines
16 KiB
C
/*
|
|
Serval Distributed Numbering Architecture (DNA)
|
|
Copyright (C) 2010-2012 Paul Gardner-Stephen
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <sys/stat.h>
|
|
#include <ctype.h>
|
|
#include "conf.h"
|
|
#include "log.h"
|
|
#include "str.h"
|
|
|
|
/* This predicate function defines the constraints on configuration option names.
|
|
Valid:
|
|
foo
|
|
foo.bar
|
|
foo.bar.chow
|
|
_word
|
|
word1
|
|
word_1
|
|
Invalid:
|
|
foo.
|
|
.foo
|
|
1foo
|
|
foo.bar.
|
|
12
|
|
1.2.3
|
|
foo bar
|
|
@author Andrew Bettison <andrew@servalproject.com>
|
|
*/
|
|
int is_configvarname(const char *arg)
|
|
{
|
|
if (arg[0] == '\0')
|
|
return 0;
|
|
if (!(isalnum(arg[0]) || arg[0] == '_'))
|
|
return 0;
|
|
const char *s = arg + 1;
|
|
for (; *s; ++s)
|
|
if (!(isalnum(*s) || *s == '_' || (*s == '.' && s[-1] != '.')))
|
|
return 0;
|
|
return s[-1] != '.';
|
|
}
|
|
|
|
#define CONFFILE_NAME "serval.conf"
|
|
#define MAX_CONFIG_VARS (100)
|
|
#define CONFIG_BUFFER_ALLOCSIZE (1024)
|
|
|
|
static int busy = 0;
|
|
static time_t config_timestamp = 0;
|
|
static char *config_buffer = NULL;
|
|
static char *config_buffer_top = NULL;
|
|
static char *config_buffer_end = NULL;
|
|
static unsigned int confc = 0;
|
|
static char *confvar[MAX_CONFIG_VARS];
|
|
static char *confvalue[MAX_CONFIG_VARS];
|
|
|
|
#define BUSY_BODY(_type, _return_if_busy, _return) \
|
|
do { if (busy) { return (_return_if_busy); } else { busy = 1; _type ret = (_return); busy = 0; return ret; } } while (0)
|
|
|
|
static char *grow_config_buffer(size_t needed)
|
|
{
|
|
size_t cursize = config_buffer_end - config_buffer;
|
|
size_t used = config_buffer_top - config_buffer;
|
|
size_t newsize = used + needed;
|
|
if (newsize > cursize) {
|
|
// Round up to nearest multiple of CONFIG_BUFFER_ALLOCSIZE.
|
|
newsize = newsize + CONFIG_BUFFER_ALLOCSIZE - ((newsize - 1) % CONFIG_BUFFER_ALLOCSIZE + 1);
|
|
char *newbuf = realloc(config_buffer, newsize);
|
|
if (newbuf == NULL) {
|
|
WHYF_perror("realloc(%llu)", newsize);
|
|
return NULL;
|
|
}
|
|
ssize_t dif = newbuf - config_buffer;
|
|
unsigned int i;
|
|
for (i = 0; i != confc; ++i) {
|
|
confvar[i] += dif;
|
|
confvalue[i] += dif;
|
|
}
|
|
config_buffer_end = newbuf + newsize;
|
|
config_buffer_top = newbuf + used;
|
|
config_buffer = newbuf;
|
|
}
|
|
char *ret = config_buffer_top;
|
|
config_buffer_top += needed;
|
|
return ret;
|
|
}
|
|
|
|
static int _read_config()
|
|
{
|
|
char conffile[1024];
|
|
if (!FORM_SERVAL_INSTANCE_PATH(conffile, CONFFILE_NAME))
|
|
return -1;
|
|
size_t size = 0;
|
|
confc = 0;
|
|
int exists = 0;
|
|
FILE *f = fopen(conffile, "r");
|
|
if (f == NULL) {
|
|
if (errno != ENOENT)
|
|
return WHYF_perror("fopen(%s)", conffile);
|
|
INFOF("non-existent config file %s", conffile);
|
|
config_timestamp = 0;
|
|
} else {
|
|
exists = 1;
|
|
struct stat st;
|
|
if (fstat(fileno(f), &st) == -1) {
|
|
WHYF_perror("fstat(%s)", conffile);
|
|
fclose(f);
|
|
return -1;
|
|
}
|
|
if (fseeko(f, (off_t) 0, SEEK_END) == -1) {
|
|
WHYF_perror("fseeko(%s, 0, SEEK_END)", conffile);
|
|
fclose(f);
|
|
return -1;
|
|
}
|
|
off_t tell = ftello(f);
|
|
if (tell == -1) {
|
|
WHYF_perror("ftello(%s)", conffile);
|
|
fclose(f);
|
|
return -1;
|
|
}
|
|
size = tell;
|
|
if (fseeko(f, (off_t) 0, SEEK_SET) == -1) {
|
|
WHYF_perror("fseeko(%s, 0, SEEK_SET)", conffile);
|
|
fclose(f);
|
|
return -1;
|
|
}
|
|
if (grow_config_buffer(size) == NULL) {
|
|
fclose(f);
|
|
return -1;
|
|
}
|
|
config_timestamp = 0;
|
|
if (fread(config_buffer, size, 1, f) != 1) {
|
|
if (ferror(f))
|
|
WHYF_perror("fread(%s, %llu)", conffile, (unsigned long long) size);
|
|
else
|
|
WHYF("fread(%s, %llu) hit EOF", conffile, (unsigned long long) size);
|
|
free(config_buffer);
|
|
config_buffer = NULL;
|
|
fclose(f);
|
|
return -1;
|
|
}
|
|
config_timestamp = st.st_mtime;
|
|
if (fclose(f) == EOF)
|
|
return WHYF_perror("fclose(%s)", conffile);
|
|
INFOF("successfully read %s", conffile);
|
|
}
|
|
config_buffer_top = config_buffer + size;
|
|
char *c = config_buffer;
|
|
char *e = config_buffer_top;
|
|
unsigned int linenum;
|
|
char *problem = NULL;
|
|
char *extra = "";
|
|
for (linenum = 1; !problem && c < e; ++linenum) {
|
|
if (*c == '#') {
|
|
// skip comment lines
|
|
while (c < e && *c != '\n')
|
|
++c;
|
|
} else if (*c == '\n') {
|
|
// skip empty lines
|
|
++c;
|
|
} else if (c < e - 1 && *c == '\r' && c[1] == '\n') {
|
|
// skip empty lines
|
|
c += 2;
|
|
} else if (confc < MAX_CONFIG_VARS) {
|
|
char *var = confvar[confc] = c;
|
|
while (c < e && *c != '=' && *c != '\r' && *c != '\n')
|
|
++c;
|
|
if (c < e && *c == '=') {
|
|
*c++ = '\0';
|
|
if (is_configvarname(var)) {
|
|
confvalue[confc] = c;
|
|
while (c < e && *c != '\r' && *c != '\n')
|
|
++c;
|
|
if (c < e && *c == '\n') {
|
|
*c++ = '\0';
|
|
++confc;
|
|
} else if (c < e - 1 && *c == '\r' && c[1] == '\n') {
|
|
*c++ = '\0';
|
|
*c++ = '\0';
|
|
++confc;
|
|
} else {
|
|
problem = "missing end-of-line";
|
|
}
|
|
} else {
|
|
problem = "invalid variable name: ";
|
|
extra = var;
|
|
}
|
|
} else {
|
|
problem = "missing '='";
|
|
}
|
|
} else {
|
|
problem = "too many variables";
|
|
}
|
|
}
|
|
if (problem)
|
|
return WHYF("Error in %s at line %u: %s%s", conffile, linenum, problem, extra);
|
|
return exists;
|
|
}
|
|
|
|
static int read_config()
|
|
{
|
|
BUSY_BODY(int, -1, _read_config());
|
|
}
|
|
|
|
/* Return true if any conf...() function is in progress, ie, a configuration fetch function is being
|
|
* re-entered. This function allows other modules to avoid this re-entry, typically the logging
|
|
* system that uses confValueGet() to set itself up, which in turn can log messages.
|
|
*
|
|
* @author Andrew Bettison <andrew@servalproject.com>
|
|
*/
|
|
int confBusy()
|
|
{
|
|
return busy;
|
|
}
|
|
|
|
/* Check if the config file has changed since we last read it, and if so, invalidate the buffer so
|
|
* that the next call to read_config() will re-load it. Returns 1 if the buffer was invalidated, 0
|
|
* if not, -1 on error.
|
|
*
|
|
* TODO: when the config system is overhauled to provide proper dynamic config reloading in JNI and
|
|
* in the servald daemon, this method will become unnecessary.
|
|
*
|
|
* @author Andrew Bettison <andrew@servalproject.com>
|
|
*/
|
|
static int _confReloadIfChanged()
|
|
{
|
|
char conffile[1024];
|
|
if (!FORM_SERVAL_INSTANCE_PATH(conffile, CONFFILE_NAME))
|
|
return -1;
|
|
struct stat st;
|
|
if (stat(conffile, &st) == -1) {
|
|
if (errno != ENOENT)
|
|
return WHYF_perror("stat(%s)", conffile);
|
|
st.st_mtime = 0;
|
|
}
|
|
if (config_timestamp != st.st_mtime) {
|
|
INFOF("detected new version of %s", conffile);
|
|
_read_config();
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
int confReloadIfChanged()
|
|
{
|
|
BUSY_BODY(int, -1, _confReloadIfChanged());
|
|
}
|
|
|
|
/* Return the number of config options.
|
|
*
|
|
* @author Andrew Bettison <andrew@servalproject.com>
|
|
*/
|
|
static int _confVarCount()
|
|
{
|
|
if (!config_buffer && _read_config() == -1)
|
|
return -1;
|
|
return confc;
|
|
}
|
|
int confVarCount()
|
|
{
|
|
BUSY_BODY(int, -1, _confVarCount());
|
|
}
|
|
|
|
/* Return the string name of the config option with the given index, which must be in the range
|
|
* 0..confVarCount(). The returned pointer is only valid until the next call to confVar() or any
|
|
* other configuration query function.
|
|
*
|
|
* @author Andrew Bettison <andrew@servalproject.com>
|
|
*/
|
|
static const char *_confVar(unsigned int index)
|
|
{
|
|
if (!config_buffer && _read_config() == -1)
|
|
return NULL;
|
|
if (index >= confc) {
|
|
WHYF("Config index=%u too big, confc=%u", index, confc);
|
|
return NULL;
|
|
}
|
|
return confvar[index];
|
|
}
|
|
const char *confVar(unsigned int index)
|
|
{
|
|
BUSY_BODY(const char *, NULL, _confVar(index));
|
|
}
|
|
|
|
/* Return the string value of the config option with the given index, which must be in the range
|
|
* 0..confVarCount(). The returned pointer is only valid until the next call to confVar() or any
|
|
* other configuration query function.
|
|
*
|
|
* @author Andrew Bettison <andrew@servalproject.com>
|
|
*/
|
|
static const char *_confValue(unsigned int index)
|
|
{
|
|
if (!config_buffer && _read_config() == -1)
|
|
return NULL;
|
|
if (index >= confc) {
|
|
WHYF("Config index=%u too big, confc=%u", index, confc);
|
|
return NULL;
|
|
}
|
|
return confvalue[index];
|
|
}
|
|
const char *confValue(unsigned int index)
|
|
{
|
|
BUSY_BODY(const char *, NULL, _confValue(index));
|
|
}
|
|
|
|
/* Return the string value of the config option with the given name. The returned pointer is only
|
|
* valid until the next call to confVarCount(), confVar(), confValue() or any other configuration
|
|
* query function. If the named config option is not defined, then returns the given default value.
|
|
* If the named config option cannot be determined for any other reason, then logs the reason and
|
|
* returns the given default value.
|
|
*
|
|
* @author Andrew Bettison <andrew@servalproject.com>
|
|
*/
|
|
static const char *_confValueGet(const char *var, const char *defaultValue)
|
|
{
|
|
if (var == NULL) {
|
|
WHYF("NULL var name, returning default value: %s", defaultValue ? defaultValue : "NULL");
|
|
return defaultValue;
|
|
}
|
|
if (!config_buffer && _read_config() == -1) {
|
|
if (defaultValue)
|
|
WARNF("Config option %s: using default value: %s", var, defaultValue);
|
|
return defaultValue;
|
|
}
|
|
unsigned int i;
|
|
for (i = 0; i != confc; ++i)
|
|
if (strcasecmp(confvar[i], var) == 0)
|
|
return confvalue[i];
|
|
return defaultValue;
|
|
}
|
|
const char *confValueGet(const char *var, const char *defaultValue)
|
|
{
|
|
BUSY_BODY(const char *, defaultValue, _confValueGet(var, defaultValue));
|
|
}
|
|
|
|
static int _confValueGetBoolean(const char *var, int defaultValue)
|
|
{
|
|
const char *value = _confValueGet(var, NULL);
|
|
if (!value)
|
|
return defaultValue;
|
|
int flag = confParseBoolean(value, var);
|
|
if (flag >= 0)
|
|
return flag;
|
|
WARNF("Config option %s: using default value %s", var, defaultValue ? "true" : "false");
|
|
return defaultValue;
|
|
}
|
|
int confValueGetBoolean(const char *var, int defaultValue)
|
|
{
|
|
BUSY_BODY(int, defaultValue, _confValueGetBoolean(var, defaultValue));
|
|
}
|
|
|
|
static int64_t _confValueGetInt64(const char *var, int64_t defaultValue)
|
|
{
|
|
const char *start = _confValueGet(var, NULL);
|
|
if (!start)
|
|
return defaultValue;
|
|
const char *end = start;
|
|
long long value = strtoll(start, (char **)&end, 10);
|
|
if (*start && !*end && end != start)
|
|
return value;
|
|
WARNF("Config option %s: '%s' is not an integer, using default value %lld", var, start, (long long) defaultValue);
|
|
return defaultValue;
|
|
}
|
|
int64_t confValueGetInt64(const char *var, int64_t defaultValue)
|
|
{
|
|
BUSY_BODY(int64_t, defaultValue, _confValueGetInt64(var, defaultValue));
|
|
}
|
|
|
|
static int64_t _confValueGetInt64Range(const char *var, int64_t defaultValue, int64_t rangemin, int64_t rangemax)
|
|
{
|
|
int64_t value = _confValueGetInt64(var, defaultValue);
|
|
if (value >= rangemin || value <= rangemax)
|
|
return value;
|
|
WARNF("Config option %s: configured value %lld out of range [%lld,%lld], using default value %lld",
|
|
var, (long long) value, (long long) rangemin, (long long) rangemax, (long long) defaultValue);
|
|
return defaultValue;
|
|
}
|
|
int64_t confValueGetInt64Range(const char *var, int64_t defaultValue, int64_t rangemin, int64_t rangemax)
|
|
{
|
|
BUSY_BODY(int64_t, defaultValue, _confValueGetInt64Range(var, defaultValue, rangemin, rangemax));
|
|
}
|
|
|
|
void confSetDebugFlags()
|
|
{
|
|
if (config_buffer || read_config() != -1) {
|
|
debugflags_t setmask = 0;
|
|
debugflags_t clearmask = 0;
|
|
int setall = 0;
|
|
int clearall = 0;
|
|
unsigned int i;
|
|
for (i = 0; i != confc; ++i) {
|
|
char *var = confvar[i];
|
|
if (strncasecmp(var, "debug.", 6) == 0) {
|
|
debugflags_t mask = debugFlagMask(var + 6);
|
|
if (mask == 0)
|
|
WARNF("Unsupported debug option '%s'", var);
|
|
else {
|
|
int flag = confParseBoolean(confvalue[i], var);
|
|
if (flag != -1) {
|
|
if (mask == DEBUG_ALL) {
|
|
if (flag) {
|
|
//DEBUGF("Set all debug flags");
|
|
setall = 1;
|
|
} else {
|
|
//DEBUGF("Clear all debug flags");
|
|
clearall = 1;
|
|
}
|
|
} else {
|
|
if (flag) {
|
|
//DEBUGF("Set %s", var);
|
|
setmask |= mask;
|
|
} else {
|
|
//DEBUGF("Clear %s", var);
|
|
clearmask |= mask;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (setall)
|
|
debug = DEBUG_ALL;
|
|
else if (clearall)
|
|
debug = 0;
|
|
debug &= ~clearmask;
|
|
debug |= setmask;
|
|
}
|
|
}
|
|
|
|
int confParseBoolean(const char *text, const char *option_name)
|
|
{
|
|
if (!strcasecmp(text, "on") || !strcasecmp(text, "yes") || !strcasecmp(text, "true") || !strcmp(text, "1"))
|
|
return 1;
|
|
if (!strcasecmp(text, "off") || !strcasecmp(text, "no") || !strcasecmp(text, "false") || !strcmp(text, "0"))
|
|
return 0;
|
|
WARNF("Config option %s: invalid boolean value '%s'", option_name, text);
|
|
return -1;
|
|
}
|
|
|
|
int confValueSet(const char *var, const char *value)
|
|
{
|
|
INFOF("var=%s defaultValue=%s",
|
|
var ? alloca_str_toprint(var) : "NULL",
|
|
value ? alloca_str_toprint(value) : "NULL"
|
|
);
|
|
if (!config_buffer && read_config() == -1)
|
|
return -1;
|
|
if (!is_configvarname(var))
|
|
return WHYF("Cannot %s %s: invalid variable name", value ? "set" : "delete", var);
|
|
if (value == NULL) {
|
|
unsigned int i;
|
|
for (i = 0; i < confc; ++i) {
|
|
if (strcasecmp(var, confvar[i]) == 0) {
|
|
--confc;
|
|
for (; i < confc; ++i) {
|
|
confvar[i] = confvar[i + 1];
|
|
confvalue[i] = confvalue[i + 1];
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
} else {
|
|
size_t valuelen = strlen(value);
|
|
unsigned int i;
|
|
for (i = 0; i != confc; ++i) {
|
|
if (strcasecmp(var, confvar[i]) == 0) {
|
|
char *valueptr = confvalue[i];
|
|
if (valuelen > strlen(valueptr)) {
|
|
if ((valueptr = grow_config_buffer(valuelen + 1)) == NULL)
|
|
return -1;
|
|
}
|
|
strcpy(confvar[i], var);
|
|
confvalue[i] = strcpy(valueptr, value);
|
|
return 0;
|
|
}
|
|
}
|
|
if (confc >= MAX_CONFIG_VARS)
|
|
return WHYF("Cannot set %s: too many variables", var);
|
|
size_t varlen = strlen(var);
|
|
char *buf = grow_config_buffer(varlen + 1 + valuelen + 1);
|
|
if (buf == NULL)
|
|
return -1;
|
|
confvar[confc] = strcpy(buf, var);
|
|
confvalue[confc] = strcpy(buf + varlen + 1, value);
|
|
++confc;
|
|
}
|
|
INFOF("config set %s = %s", var, value ? alloca_str_toprint(value) : "NULL");
|
|
return 0;
|
|
}
|
|
|
|
int confWrite()
|
|
{
|
|
if (config_buffer) {
|
|
char conffile[1024];
|
|
char tempfile[1024];
|
|
FILE *outf = NULL;
|
|
if (!FORM_SERVAL_INSTANCE_PATH(conffile, "serval.conf"))
|
|
return -1;
|
|
if (!FORM_SERVAL_INSTANCE_PATH(tempfile, "serval.conf.temp"))
|
|
return -1;
|
|
if ((outf = fopen(tempfile, "w")) == NULL)
|
|
return WHYF_perror("fopen(%s, \"w\")", tempfile);
|
|
unsigned int i;
|
|
for (i = 0; i != confc; ++i)
|
|
fprintf(outf, "%s=%s\n", confvar[i], confvalue[i]);
|
|
if (fclose(outf) == EOF)
|
|
return WHYF_perror("fclose(%s)", tempfile);
|
|
if (rename(tempfile, conffile)) {
|
|
WHYF_perror("rename(%s, %s)", tempfile, conffile);
|
|
unlink(tempfile);
|
|
return -1;
|
|
}
|
|
struct stat st;
|
|
if (stat(conffile, &st) == -1)
|
|
return WHYF_perror("stat(%s)", conffile);
|
|
config_timestamp = st.st_mtime;
|
|
INFOF("successfully wrote %s", conffile);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static char *thisinstancepath = NULL;
|
|
|
|
const char *serval_instancepath()
|
|
{
|
|
if (thisinstancepath)
|
|
return thisinstancepath;
|
|
const char *instancepath = getenv("SERVALINSTANCE_PATH");
|
|
if (!instancepath)
|
|
instancepath = DEFAULT_INSTANCE_PATH;
|
|
return instancepath;
|
|
}
|
|
|
|
void serval_setinstancepath(const char *instancepath)
|
|
{
|
|
if (thisinstancepath == NULL)
|
|
free(thisinstancepath);
|
|
|
|
thisinstancepath = strdup(instancepath);
|
|
}
|
|
|
|
int form_serval_instance_path(char *buf, size_t bufsiz, const char *path)
|
|
{
|
|
if (snprintf(buf, bufsiz, "%s/%s", serval_instancepath(), path) < bufsiz)
|
|
return 1;
|
|
WHYF("Cannot form pathname \"%s/%s\" -- buffer too small (%lu bytes)", serval_instancepath(), path, (unsigned long)bufsiz);
|
|
return 0;
|
|
}
|
|
|
|
int create_serval_instance_dir() {
|
|
return mkdirs(serval_instancepath(), 0700);
|
|
}
|