mirror of
https://github.com/matrix-construct/construct
synced 2024-11-12 04:51:08 +01:00
ircd: Improve exception reporting on newconf parsing.
This commit is contained in:
parent
f25cb78588
commit
aca6eefcd9
6 changed files with 117 additions and 54 deletions
|
@ -40,6 +40,14 @@ const char *const fatalerrstr
|
|||
%s
|
||||
)"};
|
||||
|
||||
const char *const usererrstr
|
||||
{R"(
|
||||
***
|
||||
*** A fatal startup error has occurred. Please fix the problem to continue. ***
|
||||
***
|
||||
%s
|
||||
)"};
|
||||
|
||||
bool printversion;
|
||||
bool testing_conf;
|
||||
bool cmdline;
|
||||
|
@ -73,6 +81,9 @@ try
|
|||
return 0;
|
||||
}
|
||||
|
||||
const std::string confpath(configfile?: path::get(path::IRCD_CONF));
|
||||
ircd::init(ios, confpath);
|
||||
|
||||
sigs.add(SIGHUP);
|
||||
sigs.add(SIGINT);
|
||||
sigs.add(SIGTSTP);
|
||||
|
@ -80,14 +91,19 @@ try
|
|||
sigs.add(SIGTERM);
|
||||
sigs.async_wait(sigfd_handler);
|
||||
|
||||
const std::string confpath(configfile?: path::get(path::IRCD_CONF));
|
||||
ircd::init(ios, confpath);
|
||||
|
||||
if(cmdline)
|
||||
console_spawn();
|
||||
|
||||
ios.run(); // Blocks until a clean exit or an exception comes out of it.
|
||||
}
|
||||
catch(const ircd::conf::newconf::syntax_error &e)
|
||||
{
|
||||
if(ircd::debugmode)
|
||||
throw;
|
||||
|
||||
fprintf(stderr, usererrstr, e.what());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
if(ircd::debugmode)
|
||||
|
@ -120,14 +136,6 @@ void print_version()
|
|||
printf("VERSION :%s\n", rb_lib_version());
|
||||
}
|
||||
|
||||
const char *const usererrstr
|
||||
{R"(
|
||||
***
|
||||
*** A fatal startup error has occurred. Please fix the problem to continue. ***
|
||||
***
|
||||
%s
|
||||
)"};
|
||||
|
||||
bool startup_checks()
|
||||
try
|
||||
{
|
||||
|
|
|
@ -89,7 +89,8 @@ template<class T> std::type_index make_index();
|
|||
template<class T = std::string> const T &get(client::client &, const char &, const std::string &label, const std::string &key);
|
||||
template<class T = std::string> const T &get(client::client &, const char &, const std::string &key);
|
||||
|
||||
void init(const std::string &path);
|
||||
void execute();
|
||||
void parse(const std::string &path);
|
||||
|
||||
|
||||
template<class T>
|
||||
|
|
|
@ -28,6 +28,7 @@ namespace conf {
|
|||
namespace newconf {
|
||||
|
||||
IRCD_EXCEPTION(ircd::error, error)
|
||||
IRCD_EXCEPTION(error, syntax_error)
|
||||
IRCD_EXCEPTION(error, unknown_block)
|
||||
|
||||
using key = std::string; // before the equals sign in an item
|
||||
|
|
23
ircd/conf.cc
23
ircd/conf.cc
|
@ -56,18 +56,37 @@ namespace conf {
|
|||
using namespace ircd;
|
||||
|
||||
void
|
||||
conf::init(const std::string &path)
|
||||
conf::parse(const std::string &path)
|
||||
try
|
||||
{
|
||||
bootstrap();
|
||||
parse_newconf(path);
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
log.error("Failed to parse configuration: %s", e.what());
|
||||
throw;
|
||||
}
|
||||
|
||||
void
|
||||
conf::execute()
|
||||
try
|
||||
{
|
||||
if(newconf::current.empty())
|
||||
throw error("No configuration supplied to parse and execute.");
|
||||
|
||||
// Translate to oldconf and linefeed
|
||||
bootstrap();
|
||||
newconf::translate(newconf::current, []
|
||||
(const std::string &line)
|
||||
{
|
||||
execute(line);
|
||||
});
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
log.error("Configuration init failed: %s", e.what());
|
||||
throw;
|
||||
}
|
||||
|
||||
void
|
||||
conf::bootstrap()
|
||||
|
|
18
ircd/ircd.cc
18
ircd/ircd.cc
|
@ -38,7 +38,7 @@ namespace ircd
|
|||
void handle_sigusr1();
|
||||
void handle_sigterm();
|
||||
void handle_sigquit();
|
||||
void main(const std::string confpath);
|
||||
void main() noexcept;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -57,9 +57,13 @@ ircd::init(boost::asio::io_service &io_service,
|
|||
log::init();
|
||||
log::mark("log started");
|
||||
|
||||
log::info("parsing your configuration");
|
||||
conf::parse(configfile);
|
||||
|
||||
// The master of ceremonies runs the show after this function returns and ios.run()
|
||||
// It cannot spawn when no ios is running so it is deferred just in case.
|
||||
context mc(8_MiB, std::bind(&main, configfile), ctx::DEFER_POST);
|
||||
log::debug("spawning main context");
|
||||
context mc(8_MiB, ircd::main, ctx::DEFER_POST);
|
||||
|
||||
// The context will not be joined and block this function when no parent context
|
||||
// is currently running, but it should still be detached here. It can then delete
|
||||
|
@ -70,15 +74,15 @@ ircd::init(boost::asio::io_service &io_service,
|
|||
}
|
||||
|
||||
void
|
||||
ircd::main(const std::string configfile)
|
||||
try
|
||||
ircd::main()
|
||||
noexcept try
|
||||
{
|
||||
// Ownership is taken of the main context to delete it at function end
|
||||
const custom_ptr<ctx::ctx> mc(ircd::mc, ctx::free);
|
||||
log::debug("IRCd entered main context.");
|
||||
|
||||
log::debug("setting up configuration");
|
||||
conf::init(configfile);
|
||||
log::info("executing configuration");
|
||||
conf::execute();
|
||||
|
||||
log::debug("setting up signals");
|
||||
boost::asio::signal_set sigfd(*ios);
|
||||
|
@ -100,7 +104,7 @@ try
|
|||
catch(const std::exception &e)
|
||||
{
|
||||
log::error("main context: %s", e.what());
|
||||
throw;
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -21,18 +21,17 @@
|
|||
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
|
||||
namespace ircd {
|
||||
namespace conf {
|
||||
namespace newconf {
|
||||
|
||||
letters registry;
|
||||
|
||||
} // namespace newconf
|
||||
} // namespace conf
|
||||
} // namespace ircd
|
||||
|
||||
using namespace ircd;
|
||||
|
||||
namespace qi = boost::spirit::qi;
|
||||
namespace ascii = qi::ascii;
|
||||
using qi::lexeme;
|
||||
using qi::char_;
|
||||
using qi::lit;
|
||||
using qi::eol;
|
||||
using qi::blank;
|
||||
using qi::eps;
|
||||
|
||||
/*
|
||||
registry['A'] = "admin";
|
||||
registry['B'] = "blacklist";
|
||||
|
@ -48,13 +47,9 @@ using namespace ircd;
|
|||
registry['l'] = "log";
|
||||
*/
|
||||
|
||||
namespace qi = boost::spirit::qi;
|
||||
namespace ascii = qi::ascii;
|
||||
using qi::lexeme;
|
||||
using qi::char_;
|
||||
using qi::lit;
|
||||
using qi::eol;
|
||||
using qi::blank;
|
||||
namespace ircd {
|
||||
namespace conf {
|
||||
namespace newconf {
|
||||
|
||||
using str = std::string;
|
||||
using strvec = std::vector<str>;
|
||||
|
@ -77,7 +72,7 @@ struct ignores
|
|||
|
||||
template<class iter,
|
||||
class ignores>
|
||||
struct newconf_parser
|
||||
struct parser
|
||||
:qi::grammar<iter, strvecvecvec(), ignores>
|
||||
{
|
||||
template<class ret> using rule = qi::rule<iter, ret, ignores>;
|
||||
|
@ -90,49 +85,62 @@ struct newconf_parser
|
|||
rule<strvecvec()> block;
|
||||
rule<strvecvecvec()> conf;
|
||||
|
||||
newconf_parser();
|
||||
parser();
|
||||
};
|
||||
|
||||
letters registry;
|
||||
|
||||
} // namespace newconf
|
||||
} // namespace conf
|
||||
} // namespace ircd
|
||||
|
||||
template<class iter,
|
||||
class ignores>
|
||||
newconf_parser<iter, ignores>::newconf_parser()
|
||||
:newconf_parser::base_type // pass reference the topmost level to begin parsing on
|
||||
conf::newconf::parser<iter, ignores>::parser()
|
||||
:parser::base_type // pass reference the topmost level to begin parsing on
|
||||
{
|
||||
conf
|
||||
}
|
||||
,unquoted // config values without double-quotes, cannot have ';' because that's the ending
|
||||
{
|
||||
lexeme[+(char_ - ';')]
|
||||
,"unquoted"
|
||||
}
|
||||
,quoted // config values surrounded by double quotes, which cannot have double quotes in them
|
||||
{
|
||||
lexeme['"' >> +(char_ - '"') >> '"']
|
||||
,"quoted"
|
||||
}
|
||||
,key // configuration key, must be simple alnum's with underscores
|
||||
{
|
||||
+char_("[a-zA-Z0-9_]")
|
||||
,"key"
|
||||
}
|
||||
,item // a key-value pair
|
||||
{
|
||||
+(key) >> '=' >> +(quoted | unquoted) >> ';'
|
||||
,"item"
|
||||
}
|
||||
,topitem // a key-value pair, but at the topconf no '=' is used
|
||||
{
|
||||
+(key) >> +(quoted) >> ';'
|
||||
,"topitem"
|
||||
}
|
||||
,block // a bracketed conf block, the type-label is like a key, and it can have a unique quoted label
|
||||
{
|
||||
*(key >> *(quoted)) >> '{' >> *(item) >> '}' >> ';'
|
||||
,"block"
|
||||
}
|
||||
,conf // newconf is comprised of either conf blocks or key-value's at the top level
|
||||
{
|
||||
*(block | topitem)
|
||||
,"conf"
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
template<class iter>
|
||||
ignores<iter>::ignores()
|
||||
conf::newconf::ignores<iter>::ignores()
|
||||
:ignores::base_type // pass reference to the topmost skip-parser
|
||||
{
|
||||
skip
|
||||
|
@ -156,34 +164,35 @@ ignores<iter>::ignores()
|
|||
{
|
||||
}
|
||||
|
||||
ircd::conf::newconf::topconf
|
||||
ircd::conf::newconf::parse_file(const std::string &path)
|
||||
conf::newconf::topconf
|
||||
conf::newconf::parse_file(const std::string &path)
|
||||
{
|
||||
std::ifstream file(path);
|
||||
return parse(file);
|
||||
}
|
||||
|
||||
ircd::conf::newconf::topconf
|
||||
ircd::conf::newconf::parse(std::ifstream &file)
|
||||
conf::newconf::topconf
|
||||
conf::newconf::parse(std::ifstream &file)
|
||||
{
|
||||
const std::istreambuf_iterator<char> bit(file), eit;
|
||||
return parse(std::string(bit, eit));
|
||||
}
|
||||
|
||||
ircd::conf::newconf::topconf
|
||||
ircd::conf::newconf::parse(const std::string &str)
|
||||
conf::newconf::topconf
|
||||
conf::newconf::parse(const std::string &str)
|
||||
try
|
||||
{
|
||||
using iter = std::string::const_iterator;
|
||||
|
||||
strvecvecvec vec;
|
||||
const ignores<iter> ign;
|
||||
const newconf_parser<iter, ignores<iter>> p;
|
||||
const newconf::parser<iter, ignores<iter>> p;
|
||||
if(!phrase_parse(begin(str), end(str), p, ign, vec))
|
||||
throw ircd::error("Failed to parse");
|
||||
throw syntax_error("newconf parse failed for unknown reason");
|
||||
|
||||
// The parser doesn't feed exactly into the ideal topconf format returned to the user.
|
||||
// It's a simple position-semantic vector of strings at the same level.
|
||||
// Translation for the user is here:
|
||||
// This was my first qi grammar and there's a lot wrong with it.
|
||||
// Once it's revisited it should parse directly into the ideal structures
|
||||
// returned to the user. Until then it's translated here.
|
||||
topconf top;
|
||||
for(const auto &v : vec)
|
||||
{
|
||||
|
@ -222,6 +231,27 @@ ircd::conf::newconf::parse(const std::string &str)
|
|||
|
||||
return top;
|
||||
}
|
||||
catch(const boost::spirit::qi::expectation_failure<std::string::const_iterator> &e)
|
||||
{
|
||||
const size_t character(e.last - e.first);
|
||||
const auto line_num(std::count(begin(str), begin(str)+character, '\n'));
|
||||
|
||||
size_t char_num(0);
|
||||
size_t line_start(character);
|
||||
while(line_start && str[--line_start] != '\n')
|
||||
char_num++;
|
||||
|
||||
throw syntax_error("@line %zu +%zu: expecting :%s",
|
||||
line_num,
|
||||
char_num,
|
||||
string(e.what_).c_str());
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
ircd::log::error("Unexpected newconf error during parsing: %s", e.what());
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
std::list<std::string>
|
||||
conf::newconf::translate(const topconf &top)
|
||||
|
|
Loading…
Reference in a new issue