From aca6eefcd944c8e38136e8dfd67d341eea033cc3 Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Thu, 8 Sep 2016 13:29:02 -0700 Subject: [PATCH] ircd: Improve exception reporting on newconf parsing. --- charybdis/charybdis.cc | 30 ++++++++----- include/ircd/conf.h | 3 +- include/ircd/newconf.h | 1 + ircd/conf.cc | 23 +++++++++- ircd/ircd.cc | 18 +++++--- ircd/newconf.cc | 96 +++++++++++++++++++++++++++--------------- 6 files changed, 117 insertions(+), 54 deletions(-) diff --git a/charybdis/charybdis.cc b/charybdis/charybdis.cc index 7a159f932..0b6dc011a 100644 --- a/charybdis/charybdis.cc +++ b/charybdis/charybdis.cc @@ -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 { diff --git a/include/ircd/conf.h b/include/ircd/conf.h index a4dcac800..9f6c1c65b 100644 --- a/include/ircd/conf.h +++ b/include/ircd/conf.h @@ -89,7 +89,8 @@ template std::type_index make_index(); template const T &get(client::client &, const char &, const std::string &label, const std::string &key); template 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 diff --git a/include/ircd/newconf.h b/include/ircd/newconf.h index af21c8006..a84595f4f 100644 --- a/include/ircd/newconf.h +++ b/include/ircd/newconf.h @@ -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 diff --git a/ircd/conf.cc b/ircd/conf.cc index 8c35feb8e..1c96d33c8 100644 --- a/ircd/conf.cc +++ b/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() diff --git a/ircd/ircd.cc b/ircd/ircd.cc index 276380118..e0a944c0b 100644 --- a/ircd/ircd.cc +++ b/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 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 diff --git a/ircd/newconf.cc b/ircd/newconf.cc index c1c8c69f7..346db4ac0 100644 --- a/ircd/newconf.cc +++ b/ircd/newconf.cc @@ -21,18 +21,17 @@ #include -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; @@ -77,7 +72,7 @@ struct ignores template -struct newconf_parser +struct parser :qi::grammar { template using rule = qi::rule; @@ -90,49 +85,62 @@ struct newconf_parser rule block; rule conf; - newconf_parser(); + parser(); }; +letters registry; + +} // namespace newconf +} // namespace conf +} // namespace ircd + template -newconf_parser::newconf_parser() -:newconf_parser::base_type // pass reference the topmost level to begin parsing on +conf::newconf::parser::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 -ignores::ignores() +conf::newconf::ignores::ignores() :ignores::base_type // pass reference to the topmost skip-parser { skip @@ -156,34 +164,35 @@ ignores::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 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 ign; - const newconf_parser> p; + const newconf::parser> 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 &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 conf::newconf::translate(const topconf &top)