diff --git a/include/ircd/conf.h b/include/ircd/conf.h new file mode 100644 index 000000000..6eac1900e --- /dev/null +++ b/include/ircd/conf.h @@ -0,0 +1,84 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2018 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. The +// full license for this software is available in the LICENSE file. + +#pragma once +#define HAVE_IRCD_CONF_H + +namespace ircd::conf +{ + template struct item; // doesn't exist + template struct value; // abstraction for carrying item value + template<> struct item; // base class of all conf items + template<> struct item; + + IRCD_EXCEPTION(ircd::error, error) + + extern const std::string &config; //TODO: X + + void init(const string_view &configfile); +} + +/// Conf item base class. You don't create this directly; use one of the +/// derived templates instead. +template<> +struct ircd::conf::item +:instance_list> +{ + json::strung feature_; + json::object feature; + string_view name; + + virtual bool refresh(); + + item(const json::members &); + virtual ~item() noexcept; +}; + +/// Conf item value abstraction. If possible, the conf item will also +/// inherit from this template to deduplicate functionality between +/// conf items which contain similar classes of values. +template +struct ircd::conf::value +:conf::item<> +{ + using value_type = T; + + T _value; + std::function refresher; + + operator const T &() const + { + return _value; + } + + bool refresh() override + { + return refresher? refresher(_value) : false; + } + + template + value(const json::members &memb, U&& t) + :conf::item<>{memb} + ,_value{std::forward(t)} + {} + + value(const json::members &memb = {}) + :conf::item<>{memb} + ,_value{feature.get("default", T{})} + {} +}; + +template<> +struct ircd::conf::item +:conf::value +{ + using value_type = seconds; + using value::value; +}; diff --git a/include/ircd/m/keys.h b/include/ircd/m/keys.h index 95a23d606..56ff148b3 100644 --- a/include/ircd/m/keys.h +++ b/include/ircd/m/keys.h @@ -82,7 +82,7 @@ struct ircd::m::keys struct ircd::m::keys::init { - json::object conf; + json::object config; void certificate(); void signing(); @@ -90,7 +90,7 @@ struct ircd::m::keys::init public: void bootstrap(); - init(const json::object &conf); + init(const json::object &config); ~init() noexcept; }; diff --git a/include/ircd/m/m.h b/include/ircd/m/m.h index b619411dc..c31327f91 100644 --- a/include/ircd/m/m.h +++ b/include/ircd/m/m.h @@ -61,7 +61,7 @@ namespace ircd struct ircd::m::init { - json::object conf; + json::object config; void bootstrap(); void listeners(); diff --git a/include/ircd/stdinc.h b/include/ircd/stdinc.h index 9337a31e0..6c45e556c 100644 --- a/include/ircd/stdinc.h +++ b/include/ircd/stdinc.h @@ -183,7 +183,7 @@ namespace ircd constexpr size_t BUFSIZE { 512 }; extern const enum runlevel &runlevel; - extern const std::string &conf; + extern const std::string &config; extern bool debugmode; ///< Toggle; available only ifdef RB_DEBUG std::string demangle(const std::string &symbol); @@ -218,6 +218,7 @@ namespace ircd #include "http.h" #include "fmt.h" #include "magics.h" +#include "conf.h" #include "fs/fs.h" #include "ios.h" #include "ctx/ctx.h" diff --git a/ircd/Makefile.am b/ircd/Makefile.am index d42943b60..fe01c610d 100644 --- a/ircd/Makefile.am +++ b/ircd/Makefile.am @@ -78,6 +78,7 @@ libircd_la_SOURCES = \ logger.cc \ info.cc \ sodium.cc \ + conf.cc \ rfc1459.cc \ rand.cc \ hash.cc \ diff --git a/ircd/conf.cc b/ircd/conf.cc new file mode 100644 index 000000000..a44084d0f --- /dev/null +++ b/ircd/conf.cc @@ -0,0 +1,135 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2018 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. The +// full license for this software is available in the LICENSE file. + +namespace ircd::conf +{ + std::string _config; + static std::string read_json_file(string_view filename); +} + +//TODO: X +decltype(ircd::conf::config) +ircd::conf::config +{ + _config +}; + +// The configuration file is a user-converted Synapse homeserver.yaml +// converted into JSON. The configuration file is only truly necessary +// the first time IRCd is ever run. It does not have to be passed again +// to subsequent executions of IRCd if the database can be found. If +// the database is found, data passed in the configfile will be used +// to override the databased values. In this case, the complete config +// is not required to be specified in the file; only what is present +// will be used to override. +// +// *NOTE* This expects *canonical JSON* right now. That means converting +// your homeserver.yaml may be a two step process: 1. YAML to JSON, 2. +// whitespace-stripping the JSON. Tools to do both of these things are +// first hits in a google search. +void +ircd::conf::init(const string_view &filename) +{ + _config = read_json_file(filename); +} + +// +// item +// + +template<> +decltype(ircd::instance_list>::list) +ircd::instance_list>::list +{}; + +/// Conf item abstract constructor. +ircd::conf::item::item(const json::members &opts) +:feature_ +{ + opts +} +,feature +{ + feature_ +} +,name +{ + feature.has("name")? unquote(feature["name"]) : ""_sv +} +{ +} + +ircd::conf::item::~item() +noexcept +{ +} + +bool +ircd::conf::item::refresh() +{ + return false; +} + +// +// misc +// + +std::string +ircd::conf::read_json_file(string_view filename) +try +{ + if(!filename.empty()) + log::debug + { + "User supplied a configuration file path: `%s'", filename + }; + + if(filename.empty()) + filename = fs::CPATH; + + if(!fs::exists(filename)) + return {}; + + std::string read + { + fs::read(filename) + }; + + // ensure any trailing cruft is removed to not set off the validator + if(endswith(read, '\n')) + read.pop_back(); + + if(endswith(read, '\r')) + read.pop_back(); + + // grammar check; throws on error + json::valid(read); + + const json::object object{read}; + const size_t key_count{object.count()}; + log::info + { + "Using configuration from: `%s': JSON object with %zu members in %zu bytes", + filename, + key_count, + read.size() + }; + + return read; +} +catch(const std::exception &e) +{ + log::error + { + "Configuration @ `%s': %s", filename, e.what() + }; + + throw; +} diff --git a/ircd/ircd.cc b/ircd/ircd.cc index 68a979098..fe9dae6f7 100644 --- a/ircd/ircd.cc +++ b/ircd/ircd.cc @@ -20,13 +20,9 @@ namespace ircd boost::asio::io_context *ios; // user's io service struct strand *strand; // libircd event serializer - std::string _conf; // JSON read from configfile - const std::string &conf{_conf}; // Observer for conf data - ctx::ctx *main_context; // Main program loop bool debugmode; // meaningful ifdef RB_DEBUG - std::string read_conf(std::string file); void set_runlevel(const enum runlevel &); void at_main_exit() noexcept; void main(); @@ -87,30 +83,16 @@ try // Saves the user's runlevel_changed callback which we invoke. ircd::runlevel_changed = std::move(runlevel_changed); - // The log is available, but it is console-only until conf opens files. + // The log is available. but it is console-only until conf opens files. log::init(); log::mark("READY"); + // The conf supplied by the user is read in; see: ircd::conf. + conf::init(configfile); + // This starts off the log with library information. info::init(); - // The configuration file is a user-converted Synapse homeserver.yaml - // converted into JSON. The configuration file is only truly meaningful - // the first time IRCd is ever run. Subsequently, only the database must - // be found. Real configuration is stored in the !config channel. - // - // This subroutine reads a file either at the user-supplied path or the - // default path specified in ircd::fs, vets for basic syntax issues, and - // then returns a string of JSON (the file's contents). The validity of - // the actual configuration is not known until specific subsystems are - // init'ed later. - // - // *NOTE* This expects *canonical JSON* right now. That means converting - // your homeserver.yaml may be a two step process: 1. YAML to JSON, 2. - // whitespace-stripping the JSON. Tools to do both of these things are - // first hits in a google search. - ircd::_conf = read_conf(configfile); - // Setup the main context, which is a new stack executing the function // ircd::main(). The main_context is the first ircd::ctx to be spawned // and will be the last to finish. @@ -362,56 +344,3 @@ ircd::reflect(const enum runlevel &level) return "??????"; } - -std::string -ircd::read_conf(std::string filename) -try -{ - if(!filename.empty()) - log::debug - { - "User supplied a configuration file path: `%s'", filename - }; - - if(filename.empty()) - filename = fs::CPATH; - - if(!fs::exists(filename)) - return {}; - - std::string read - { - fs::read(filename) - }; - - // ensure any trailing cruft is removed to not set off the validator - if(endswith(read, '\n')) - read.pop_back(); - - if(endswith(read, '\r')) - read.pop_back(); - - // grammar check; throws on error - json::valid(read); - - const json::object object{read}; - const size_t key_count{object.count()}; - log::info - { - "Using configuration from: `%s': JSON object with %zu members in %zu bytes", - filename, - key_count, - read.size() - }; - - return read; -} -catch(const std::exception &e) -{ - log::error - { - "Configuration @ `%s': %s", filename, e.what() - }; - - throw; -} diff --git a/ircd/m/keys.cc b/ircd/m/keys.cc index f11c238a5..7e5787304 100644 --- a/ircd/m/keys.cc +++ b/ircd/m/keys.cc @@ -52,8 +52,8 @@ ircd::m::self::tls_cert_der_sha256_b64 // init // -ircd::m::keys::init::init(const json::object &conf) -:conf{conf} +ircd::m::keys::init::init(const json::object &config) +:config{config} { certificate(); signing(); @@ -114,7 +114,7 @@ ircd::m::keys::init::signing() { const std::string sk_file { - unquote(conf.get("signing_key_path", "construct.sk")) + unquote(config.get("signing_key_path", "construct.sk")) }; if(fs::exists(sk_file)) diff --git a/ircd/m/m.cc b/ircd/m/m.cc index 513cd364c..95682dcee 100644 --- a/ircd/m/m.cc +++ b/ircd/m/m.cc @@ -66,13 +66,13 @@ ircd::m::control ircd::m::init::init() try -:conf +:config { - ircd::conf + ircd::conf::config //TODO: X } ,_keys { - conf + this->config } { modules(); @@ -123,8 +123,8 @@ ircd::m::init::modules() namespace ircd::m { - static void init_listener(const json::object &conf, const json::object &opts, const string_view &bindaddr); - static void init_listener(const json::object &conf, const json::object &opts); + static void init_listener(const json::object &config, const json::object &opts, const string_view &bindaddr); + static void init_listener(const json::object &config, const json::object &opts); } void @@ -132,18 +132,18 @@ ircd::m::init::listeners() { const json::array listeners { - conf["listeners"] + config["listeners"] }; if(m::listeners.empty()) - init_listener(conf, {}); + init_listener(config, {}); else for(const json::object opts : listeners) - init_listener(conf, opts); + init_listener(config, opts); } static void -ircd::m::init_listener(const json::object &conf, +ircd::m::init_listener(const json::object &config, const json::object &opts) { const json::array binds @@ -152,14 +152,14 @@ ircd::m::init_listener(const json::object &conf, }; if(binds.empty()) - init_listener(conf, opts, "0.0.0.0"); + init_listener(config, opts, "0.0.0.0"); else for(const auto &bindaddr : binds) - init_listener(conf, opts, unquote(bindaddr)); + init_listener(config, opts, unquote(bindaddr)); } static void -ircd::m::init_listener(const json::object &conf, +ircd::m::init_listener(const json::object &config, const json::object &opts, const string_view &host) { @@ -175,12 +175,12 @@ ircd::m::init_listener(const json::object &conf, // Translate synapse options to our options (which reflect asio::ssl) const json::strung options{json::members { - { "name", name }, - { "host", host }, - { "port", opts.get("port", 8448L) }, - { "ssl_certificate_file_pem", conf["tls_certificate_path"] }, - { "ssl_private_key_file_pem", conf["tls_private_key_path"] }, - { "ssl_tmp_dh_file", conf["tls_dh_params_path"] }, + { "name", name }, + { "host", host }, + { "port", opts.get("port", 8448L) }, + { "ssl_certificate_file_pem", config["tls_certificate_path"] }, + { "ssl_private_key_file_pem", config["tls_private_key_path"] }, + { "ssl_tmp_dh_file", config["tls_dh_params_path"] }, }}; m::listeners.emplace_back(options);