diff --git a/include/villas/config.hpp b/include/villas/config.hpp new file mode 100644 index 000000000..deea164e0 --- /dev/null +++ b/include/villas/config.hpp @@ -0,0 +1,82 @@ +/** Configuration file parsing. + * + * @file + * @author Steffen Vogel + * @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASnode + * + * 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 3 of the License, or + * 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, see . + *********************************************************************************/ + +#pragma once + +#include + +#include +#include + +namespace villas { +namespace node { + +class Config { + +protected: + std::string uri; + + FILE *local_file; + AFILE *remote_file; + + Logger logger; + + /** Check if file exists on local system. */ + static bool isLocalFile(const std::string &uri) + { + return access(uri.c_str(), F_OK) != -1; + } + + /** Decode configuration file. */ + void decode(); + +#ifdef LIBCONFIG_FOUND + /** Convert libconfig .conf file to libjansson .json file. */ + void libconfigDecode(); +#endif /* LIBCONFIG_FOUND */ + + /** Load configuration from standard input (stdim). */ + void loadFromStdio(); + + /** Load configuration from local file. */ + void loadFromLocalFile(const std::string &u); + + /** Load configuration from a remote URI via advio. */ + void loadFromRemoteFile(const std::string &u); + +public: + json_t *root; + + Config(); + Config(const std::string &u); + + ~Config(); + + void load(const std::string &u); + + /** Pretty-print libjansson error. */ + void prettyPrintError(json_error_t err); +}; + +} // namespace node +} // namespace villas diff --git a/include/villas/super_node.hpp b/include/villas/super_node.hpp index aa8ca2ded..671361ce7 100644 --- a/include/villas/super_node.hpp +++ b/include/villas/super_node.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -42,10 +43,6 @@ protected: int idleStop; - int priority; /**< Process priority (lower is better) */ - int affinity; /**< Process affinity of the server and all created threads */ - int hugepages; /**< Number of hugepages to reserve. */ - Logger logger; struct vlist nodes; @@ -60,12 +57,16 @@ protected: Web web; #endif + int priority; /**< Process priority (lower is better) */ + int affinity; /**< Process affinity of the server and all created threads */ + int hugepages; /**< Number of hugepages to reserve. */ + struct task task; /**< Task for periodic stats output */ std::string name; /**< A name of this super node. Usually the hostname. */ std::string uri; /**< URI of configuration */ - json_t *json; /**< JSON representation of the configuration. */ + Config config; /** The configuration file. */ public: /** Inititalize configuration object before parsing the configuration. */ @@ -73,14 +74,14 @@ public: int init(); - /** Wrapper for parse() */ - void parseUri(const std::string &name); + /** Wrapper for parse() which loads the config first. */ + void parse(const std::string &name); /** Parse super-node configuration. * * @param cfg A libjansson object which contains the configuration. */ - void parseJson(json_t *cfg); + void parse(json_t *cfg); /** Check validity of super node configuration. */ void check(); @@ -148,7 +149,7 @@ public: json_t * getConfig() { - return json; + return config.root; } std::string getConfigUri() diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 3d5278e8e..16da40ef7 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -42,6 +42,7 @@ endif() set(LIB_SRC super_node.cpp config_helper.cpp + config.cpp memory/heap.c memory/hugepage.c memory/managed.c diff --git a/lib/config.cpp b/lib/config.cpp new file mode 100644 index 000000000..408a80d1c --- /dev/null +++ b/lib/config.cpp @@ -0,0 +1,190 @@ + +/** Configuration file parsing. + * + * @author Steffen Vogel + * @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASnode + * + * 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 3 of the License, or + * 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, see . + *********************************************************************************/ + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace villas; +using namespace villas::node; + +Config::Config() : + local_file(nullptr), + remote_file(nullptr), + root(nullptr) +{ + logger = logging.get("config"); +} + +Config::Config(const std::string &u) : + Config() +{ + load(u); +} + +Config::~Config() +{ + /* Close configuration file */ + if (remote_file) + afclose(remote_file); + else if (local_file != stdin) + fclose(local_file); + + if (root) + json_decref(root); +} + +void Config::load(const std::string &u) +{ + if (u == "-") + loadFromStdio(); + else if (isLocalFile(u)) + loadFromLocalFile(u); + else + loadFromRemoteFile(u); + + decode(); +} + +void Config::loadFromStdio() +{ + logger->info("Reading configuration from standard input"); + + local_file = stdin; +} + +void Config::loadFromLocalFile(const std::string &u) +{ + logger->info("Reading configuration from local file: {}", u); + + local_file = fopen(u.c_str(), "r"); + if (!local_file) + throw RuntimeError("Failed to open configuration from: {}", u); + + uri = u; +} + +void Config::loadFromRemoteFile(const std::string &u) +{ + logger->info("Reading configuration from remote URI: {}", u); + + remote_file = afopen(u.c_str(), "r"); + if (!remote_file) + throw RuntimeError("Failed to open configuration from: {}", u); + + local_file = remote_file->file; + + uri = u; +} + +void Config::decode() +{ + json_error_t err; + + root = json_loadf(local_file, 0, &err); + if (root == nullptr) { +#ifdef LIBCONFIG_FOUND + /* We try again to parse the config in the legacy format */ + libconfigDecode(); +#else + throw JanssonParseError(err); +#endif /* LIBCONFIG_FOUND */ + } +} + +void Config::libconfigDecode() +{ + int ret; + + config_t cfg; + config_setting_t *cfg_root; + config_init(&cfg); + config_set_auto_convert(&cfg, 1); + + /* Setup libconfig include path. + * This is only supported for local files */ + if (isLocalFile(uri)) { + char *cpy = strdup(uri.c_str()); + + config_set_include_dir(&cfg, dirname(cpy)); + + free(cpy); + } + + if (remote_file) + arewind(remote_file); + else + rewind(local_file); + + ret = config_read(&cfg, local_file); + if (ret != CONFIG_TRUE) + throw LibconfigParseError(&cfg); + + cfg_root = config_root_setting(&cfg); + + root = config_to_json(cfg_root); + if (root == nullptr) + throw RuntimeError("Failed to convert JSON to configuration file"); + + config_destroy(&cfg); +} + +void Config::prettyPrintError(json_error_t err) +{ + std::ifstream infile(uri); + std::string line; + + int context = 4; + int start_line = err.line - context; + int end_line = err.line + context; + + for (int line_no = 0; std::getline(infile, line); line_no++) { + if (line_no < start_line || line_no > end_line) + continue; + + std::cerr << std::setw(4) << std::right << line_no << " " BOX_UD " " << line << std::endl; + + if (line_no == err.line) { + for (int col = 0; col < err.column; col++) + std::cerr << " "; + + std::cerr << BOX_UD; + + for (int col = 0; col < err.column; col++) + std::cerr << " "; + + std::cerr << BOX_UR << err.text << std::endl; + std::cerr << std::endl; + } + } +} diff --git a/lib/super_node.cpp b/lib/super_node.cpp index ebdac6b70..9f83fe7c2 100644 --- a/lib/super_node.cpp +++ b/lib/super_node.cpp @@ -22,8 +22,6 @@ #include #include -#include -#include #include #include @@ -50,16 +48,15 @@ using namespace villas::node; SuperNode::SuperNode() : state(STATE_INITIALIZED), idleStop(false), - priority(0), - affinity(0), - hugepages(DEFAULT_NR_HUGEPAGES), #ifdef WITH_API api(this), #ifdef WITH_WEB web(&api), #endif + priority(0), + affinity(0), + hugepages(DEFAULT_NR_HUGEPAGES) #endif - json(nullptr) { nodes.state = STATE_DESTROYED; paths.state = STATE_DESTROYED; @@ -81,89 +78,14 @@ SuperNode::SuperNode() : logger = logging.get("super_node"); } -void SuperNode::parseUri(const std::string &u) +void SuperNode::parse(const std::string &u) { - json_error_t err; + config.load(u); - FILE *f; - AFILE *af; - - /* Via stdin */ - if (u == "-") { - logger->info("Reading configuration from standard input"); - - af = nullptr; - f = stdin; - } - else { - logger->info("Reading configuration from URI: {}", u); - - af = afopen(u.c_str(), "r"); - if (!af) - throw RuntimeError("Failed to open configuration from: {}", u); - - f = af->file; - } - - /* Parse config */ - json = json_loadf(f, 0, &err); - if (json == nullptr) { -#ifdef LIBCONFIG_FOUND - int ret; - - config_t cfg; - config_setting_t *json_root = nullptr; - - config_init(&cfg); - config_set_auto_convert(&cfg, 1); - - /* Setup libconfig include path. - * This is only supported for local files */ - if (access(u.c_str(), F_OK) != -1) { - char *cpy = strdup(u.c_str()); - - config_set_include_dir(&cfg, dirname(cpy)); - - free(cpy); - } - - if (af) - arewind(af); - else - rewind(f); - - ret = config_read(&cfg, f); - if (ret != CONFIG_TRUE) { - logger->warn("conf: {} in {}:{}", config_error_text(&cfg), u.c_str(), config_error_line(&cfg)); - logger->warn("json: {} in {}:{}:{}", err.text, err.source, err.line, err.column); - logger->error("Failed to parse configuration"); - killme(SIGABRT); - } - - json_root = config_root_setting(&cfg); - - json = config_to_json(json_root); - if (json == nullptr) - throw RuntimeError("Failed to convert JSON to configuration file"); - - config_destroy(&cfg); -#else - throw JsonError(err, "Failed to parse configuration file"); -#endif /* LIBCONFIG_FOUND */ - } - - /* Close configuration file */ - if (af) - afclose(af); - else if (f != stdin) - fclose(f); - - uri = u; - - parseJson(json); + parse(config.root); } -void SuperNode::parseJson(json_t *j) +void SuperNode::parse(json_t *cfg) { int ret; const char *nme = nullptr; @@ -179,7 +101,7 @@ void SuperNode::parseJson(json_t *j) idleStop = true; - ret = json_unpack_ex(j, &err, JSON_STRICT, "{ s?: o, s?: o, s?: o, s?: o, s?: i, s?: i, s?: i, s?: s, s?: b }", + ret = json_unpack_ex(cfg, &err, JSON_STRICT, "{ s?: o, s?: o, s?: o, s?: o, s?: i, s?: i, s?: i, s?: s, s?: b }", "http", &json_web, "logging", &json_logging, "nodes", &json_nodes, @@ -285,8 +207,6 @@ parse: path *p = (path *) alloc(sizeof(path)); } } - json = j; - state = STATE_PARSED; } @@ -558,9 +478,6 @@ SuperNode::~SuperNode() vlist_destroy(&paths, (dtor_cb_t) path_destroy, true); vlist_destroy(&nodes, (dtor_cb_t) node_destroy, true); vlist_destroy(&interfaces, (dtor_cb_t) if_destroy, true); - - if (json) - json_decref(json); } int SuperNode::periodic() diff --git a/src/villas-node.cpp b/src/villas-node.cpp index f31b7e621..2ced3c76a 100644 --- a/src/villas-node.cpp +++ b/src/villas-node.cpp @@ -172,7 +172,7 @@ int main(int argc, char *argv[]) throw RuntimeError("Failed to initialize signal subsystem"); if (uri) - sn.parseUri(uri); + sn.parse(uri); else logger->warn("No configuration file specified. Starting unconfigured. Use the API to configure this instance."); diff --git a/src/villas-pipe.cpp b/src/villas-pipe.cpp index 3d9d53e3d..eea2ff2db 100644 --- a/src/villas-pipe.cpp +++ b/src/villas-pipe.cpp @@ -350,7 +350,7 @@ check: if (optarg == endptr) throw RuntimeError("Failed to initialize signals"); if (uri) - sn.parseUri(uri); + sn.parse(uri); else logger->warn("No configuration file specified. Starting unconfigured. Use the API to configure this instance."); diff --git a/src/villas-test-rtt.cpp b/src/villas-test-rtt.cpp index 8429941dd..00240c218 100644 --- a/src/villas-test-rtt.cpp +++ b/src/villas-test-rtt.cpp @@ -152,7 +152,7 @@ check: if (optarg == endptr) throw RuntimeError("Failed to initialize signals subsystem"); if (uri) - sn.parseUri(uri); + sn.parse(uri); else logger->warn("No configuration file specified. Starting unconfigured. Use the API to configure this instance.");