mirror of
https://github.com/servalproject/serval-dna.git
synced 2024-12-18 12:56:29 +00:00
92fa6c196a
Rename the logging primitive functions and utility functions, prefixing all with 'serval_log', eg: logMessage() -> serval_logf() etc. Add an XPRINTF xhexdump() function and use it to implement the serval_log_hexdump() utility, renamed from dump(). Add macros WHY_dump(), WARN_dump(), HINT_dump() and DEBUG_dump(), and use them everywhere. Remove the 'log.console.dump_config' and 'log.file.dump_config' configuration options; configuration is now dumped in every log prolog. The logging system now constructs the log prolog by invoking the new 'log_prolog' trigger, so that it no longer depends on the version string and configuration system. Any system that wants to present a message in the log prolog can define its own trigger, which calls standard log primitives to print the message. Split the logging system into a front-end (log.c) that provides the logging primitives and is independent of the configuration system, and a set of back-end "outputters" (log_output_console.c, log_output_file.c, log_output_android.c) that may depend on the configuration system and are decoupled from the front-end using the 'logoutput' link section. These log outputters are explicitly linked into executables by the Makefile rules, but could also be linked in using USE_FEATURE(). The USE_FEATURE() calls have _not_ been added to servald_features.c, so that different daemon executables can be built with the same feature set but different log outputs.
327 lines
9.6 KiB
C
327 lines
9.6 KiB
C
/*
|
|
Serval DNA configuration
|
|
Copyright (C) 2012 Serval Project Inc.
|
|
|
|
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 <time.h>
|
|
#include <inttypes.h> // for PRIu64 on Android
|
|
#include "conf.h"
|
|
#include "instance.h"
|
|
#include "log.h"
|
|
#include "log_prolog.h"
|
|
#include "debug.h"
|
|
#include "str.h"
|
|
#include "mem.h"
|
|
#include "os.h"
|
|
|
|
#define CONFFILE_NAME "serval.conf"
|
|
|
|
struct cf_om_node *cf_om_root = NULL;
|
|
static __thread struct file_meta conffile_meta = FILE_META_UNKNOWN;
|
|
|
|
__thread int cf_initialised = 0;
|
|
__thread int cf_limbo = 1;
|
|
__thread struct config_main config;
|
|
static __thread struct file_meta config_meta = FILE_META_UNKNOWN;
|
|
|
|
static const char *conffile_path()
|
|
{
|
|
static char path[1024] = "";
|
|
if (!path[0] && !FORMF_SERVAL_ETC_PATH(path, CONFFILE_NAME))
|
|
abort();
|
|
return path;
|
|
}
|
|
|
|
static int reload(const char *path, int *resultp)
|
|
{
|
|
DEBUGF(config, " file path=%s", alloca_str_toprint(path));
|
|
struct file_meta meta;
|
|
if (get_file_meta(path, &meta) == -1)
|
|
return -1;
|
|
DEBUGF(config, " file meta=%s", alloca_file_meta(&meta));
|
|
DEBUGF(config, "conffile_meta=%s", alloca_file_meta(&conffile_meta));
|
|
if (cmp_file_meta(&meta, &conffile_meta) == 0)
|
|
return 0;
|
|
if (conffile_meta.mtime.tv_sec != -1)
|
|
INFOF("config file %s -- detected new version", path);
|
|
char *buf = NULL;
|
|
if (meta.mtime.tv_sec == -1) {
|
|
WARNF("config file %s does not exist -- using all defaults", path);
|
|
} else if (meta.size > CONFIG_FILE_MAX_SIZE) {
|
|
WHYF("config file %s is too big (%ju bytes exceeds limit %d)", path, (uintmax_t)meta.size, CONFIG_FILE_MAX_SIZE);
|
|
return -1;
|
|
} else if (meta.size <= 0) {
|
|
WARNF("config file %s is zero size -- using all defaults", path);
|
|
} else {
|
|
FILE *f = fopen(path, "r");
|
|
if (f == NULL) {
|
|
WHYF_perror("fopen(%s)", path);
|
|
return -1;
|
|
}
|
|
if ((buf = emalloc(meta.size)) == NULL) {
|
|
fclose(f);
|
|
return -1;
|
|
}
|
|
if (fread(buf, meta.size, 1, f) != 1) {
|
|
if (ferror(f))
|
|
WHYF_perror("fread(%s, %"PRIu64")", path, (uint64_t) meta.size);
|
|
else
|
|
WHYF("fread(%s, %"PRIu64") hit EOF", path, (uint64_t) meta.size);
|
|
free(buf);
|
|
fclose(f);
|
|
return -1;
|
|
}
|
|
if (fclose(f) == EOF) {
|
|
WHYF_perror("fclose(%s)", path);
|
|
free(buf);
|
|
return -1;
|
|
}
|
|
DEBUGF(config, "config file %s successfully read %ld bytes", path, (long) meta.size);
|
|
}
|
|
conffile_meta = meta;
|
|
DEBUGF(config, "set conffile_meta=%s", alloca_file_meta(&conffile_meta));
|
|
struct cf_om_node *new_root = NULL;
|
|
*resultp = cf_om_parse(path, buf, meta.size, &new_root);
|
|
free(buf);
|
|
if (*resultp == CFERROR)
|
|
return -1;
|
|
cf_om_free_node(&cf_om_root);
|
|
cf_om_root = new_root;
|
|
return 1;
|
|
}
|
|
|
|
int cf_om_reload()
|
|
{
|
|
int result;
|
|
return reload(conffile_path(), &result);
|
|
}
|
|
|
|
int cf_om_load()
|
|
{
|
|
conffile_meta = FILE_META_UNKNOWN;
|
|
return cf_om_reload();
|
|
}
|
|
|
|
int cf_om_save()
|
|
{
|
|
if (cf_om_root) {
|
|
const char *path = conffile_path();
|
|
struct file_meta meta;
|
|
if (get_file_meta(path, &meta) == -1)
|
|
return -1;
|
|
char tempfile[1024];
|
|
FILE *outf = NULL;
|
|
if (!FORMF_SERVAL_ETC_PATH(tempfile, CONFFILE_NAME ".temp"))
|
|
return -1;
|
|
if ((outf = fopen(tempfile, "w")) == NULL)
|
|
return WHYF_perror("fopen(%s, \"w\")", tempfile);
|
|
struct cf_om_iterator it;
|
|
for (cf_om_iter_start(&it, cf_om_root); it.node; cf_om_iter_next(&it))
|
|
if (it.node->text)
|
|
fprintf(outf, "%s=%s\n", it.node->fullkey, it.node->text);
|
|
if (fclose(outf) == EOF)
|
|
return WHYF_perror("fclose(%s)", tempfile);
|
|
// rename(2) is atomic, so no other process will read a half-written file.
|
|
if (rename(tempfile, path)) {
|
|
WHYF_perror("rename(%s, %s)", tempfile, path);
|
|
unlink(tempfile);
|
|
return -1;
|
|
}
|
|
struct file_meta newmeta;
|
|
int r = alter_file_meta(path, &meta, &newmeta);
|
|
if (r == -1)
|
|
return -1;
|
|
if (r)
|
|
DEBUGF(config, "wrote %s; set mtime=%s", path, alloca_time_t(newmeta.mtime.tv_sec));
|
|
else if (cmp_file_meta(&meta, &newmeta) == 0)
|
|
WARNF("wrote %s; mtime not altered", path);
|
|
else
|
|
DEBUGF(config, "wrote %s", path);
|
|
conffile_meta = newmeta;
|
|
DEBUGF(config, "set conffile_meta=%s", alloca_file_meta(&conffile_meta));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int cf_init()
|
|
{
|
|
if (!cf_initialised) {
|
|
memset(&config, 0, sizeof config);
|
|
if (cf_dfl_config_main(&config) == CFERROR)
|
|
return -1;
|
|
conffile_meta = config_meta = FILE_META_UNKNOWN;
|
|
cf_limbo = 1;
|
|
cf_initialised = 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* (Re-)load and parse the configuration file.
|
|
*
|
|
* The 'strict' flag controls whether this function will load a defective config file. If nonzero,
|
|
* then it will not load a defective file, but will log an error and return -1. If zero, then it
|
|
* will load a defective file and use the 'permissive' flag to decide what to log and return.
|
|
*
|
|
* The 'permissive' flag only applies if 'strict' is zero, and determines how this function deals
|
|
* with a loaded defective config file. If 'permissive' is zero, then it logs an error and returns
|
|
* -1. If nonzero, then it logs a warning and returns 0.
|
|
*
|
|
* @author Andrew Bettison <andrew@servalproject.com>
|
|
*/
|
|
static int reload_and_parse(int permissive, int strict)
|
|
{
|
|
int result = CFOK;
|
|
int changed = 0;
|
|
assert(cf_initialised);
|
|
if (cf_limbo)
|
|
result = cf_dfl_config_main(&config);
|
|
if (result == CFOK || result == CFEMPTY) {
|
|
if (reload(conffile_path(), &result) == -1)
|
|
result = CFERROR;
|
|
else if (!cf_limbo && cmp_file_meta(&conffile_meta, &config_meta) == 0)
|
|
return 0;
|
|
else {
|
|
config_meta = conffile_meta;
|
|
if (result == CFOK || result == CFEMPTY) {
|
|
struct config_main new_config;
|
|
int update = 0;
|
|
memset(&new_config, 0, sizeof new_config);
|
|
result = cf_dfl_config_main(&new_config);
|
|
if (result == CFOK || result == CFEMPTY) {
|
|
result = cf_om_root ? cf_opt_config_main(&new_config, cf_om_root) : CFEMPTY;
|
|
if (result == CFOK || result == CFEMPTY) {
|
|
result = CFOK;
|
|
update = 1;
|
|
} else if (result != CFERROR && !strict) {
|
|
result &= ~CFEMPTY; // don't log "empty" as a problem
|
|
update = 1;
|
|
}
|
|
}
|
|
if (update && cf_cmp_config_main(&config, &new_config) != 0) {
|
|
config = new_config;
|
|
changed = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
int ret = changed;
|
|
if (result != CFOK) {
|
|
strbuf b = strbuf_alloca(180);
|
|
strbuf_cf_flag_reason(b, result);
|
|
if (strict)
|
|
ret = WHYF("defective config file %s not loaded -- %s", conffile_path(), strbuf_str(b));
|
|
else {
|
|
if (!permissive)
|
|
ret = WHYF("config file %s loaded despite defects -- %s", conffile_path(), strbuf_str(b));
|
|
else
|
|
WARNF("config file %s loaded despite defects -- %s", conffile_path(), strbuf_str(b));
|
|
}
|
|
}
|
|
// Dump the log file before setting cf_limbo=0, so that:
|
|
// - if we are currently in limbo this will print no dump, then as soon as limbo goes off the
|
|
// very next log message will emit the prolog at the top of the log, which includes a dump of
|
|
// the newly loaded configuration;
|
|
// - if not currently in limbo, then will print a dump, which may differ from the dump that
|
|
// already appears in the log's prolog.
|
|
cf_dump_to_log("Configuration reloaded");
|
|
cf_limbo = 0; // let log messages out
|
|
serval_log_flush();
|
|
if (changed) {
|
|
CALL_TRIGGER(conf_change);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Put a dummy no-op trigger callback into the "config_change" trigger section, otherwise if no
|
|
// other object provides one, the link will fail with errors like:
|
|
// undefined reference to `__start_tr_config_change'
|
|
// undefined reference to `__stop_tr_config_change'
|
|
|
|
static void __dummy_on_config_change() {}
|
|
DEFINE_TRIGGER(conf_change, __dummy_on_config_change);
|
|
|
|
// The configuration API entry points.
|
|
|
|
int cf_load()
|
|
{
|
|
conffile_meta = config_meta = FILE_META_UNKNOWN;
|
|
return reload_and_parse(0, 0);
|
|
}
|
|
|
|
int cf_load_strict()
|
|
{
|
|
conffile_meta = config_meta = FILE_META_UNKNOWN;
|
|
return reload_and_parse(0, 1);
|
|
}
|
|
|
|
int cf_load_permissive()
|
|
{
|
|
conffile_meta = config_meta = FILE_META_UNKNOWN;
|
|
return reload_and_parse(1, 0);
|
|
}
|
|
|
|
int cf_reload()
|
|
{
|
|
return reload_and_parse(0, 0);
|
|
}
|
|
|
|
int cf_reload_strict()
|
|
{
|
|
return reload_and_parse(0, 1);
|
|
}
|
|
|
|
int cf_reload_permissive()
|
|
{
|
|
return reload_and_parse(1, 0);
|
|
}
|
|
|
|
void cf_dump_to_log(const char *heading)
|
|
{
|
|
if (cf_limbo)
|
|
return;
|
|
struct cf_om_node *root = NULL;
|
|
int ret = cf_fmt_config_main(&root, &config);
|
|
if (ret == CFERROR) {
|
|
WHY("cannot dump current configuration: cf_fmt_config_main() returned CFERROR");
|
|
} else {
|
|
NOWHENCE(INFOF("%s:", heading));
|
|
struct cf_om_iterator oit;
|
|
int empty = 1;
|
|
for (cf_om_iter_start(&oit, root); oit.node; cf_om_iter_next(&oit)) {
|
|
if (oit.node->text && oit.node->line_number) {
|
|
empty = 0;
|
|
NOWHENCE(INFOF(" %s=%s", oit.node->fullkey, oit.node->text));
|
|
}
|
|
}
|
|
if (empty)
|
|
NOWHENCE(INFO(" (empty)"));
|
|
}
|
|
cf_om_free_node(&root);
|
|
}
|
|
|
|
|
|
/* Dump the configuration in the prolog of every log file.
|
|
*/
|
|
|
|
static void log_prolog_dump_config() {
|
|
cf_dump_to_log("Current configuration");
|
|
}
|
|
DEFINE_TRIGGER(log_prolog, log_prolog_dump_config);
|