From 465a3d3e43845a91dc7d343d07987fb4c89422aa Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 22 Jan 2019 09:13:26 -0500 Subject: [PATCH] redo ini parser --- CMakeLists.txt | 1 + include/llarp.hpp | 5 +- llarp/config.cpp | 71 +++-- llarp/context.cpp | 6 +- llarp/service/config.cpp | 16 +- llarp/util/ini.cpp | 139 +++++++++ llarp/util/ini.hpp | 466 +++--------------------------- test/util/test_llarp_util_ini.cpp | 51 ++++ 8 files changed, 282 insertions(+), 473 deletions(-) create mode 100644 test/util/test_llarp_util_ini.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 47fb3d2c8..48e7f8cdd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -634,6 +634,7 @@ set(TEST_SRC test/util/test_llarp_util_aligned.cpp test/util/test_llarp_util_bencode.cpp test/util/test_llarp_util_encode.cpp + test/util/test_llarp_util_ini.cpp test/util/test_llarp_util_queue_manager.cpp test/util/test_llarp_util_queue.cpp test/util/test_llarp_util_thread_pool.cpp diff --git a/include/llarp.hpp b/include/llarp.hpp index 6b1472d83..2b002b761 100644 --- a/include/llarp.hpp +++ b/include/llarp.hpp @@ -65,9 +65,8 @@ namespace llarp HandleSignal(int sig); private: - - void - SetPIDFile(const std::string & fname); + void + SetPIDFile(const std::string &fname); bool WritePIDFile() const; diff --git a/llarp/config.cpp b/llarp/config.cpp index cbac563b0..2e16fb2d6 100644 --- a/llarp/config.cpp +++ b/llarp/config.cpp @@ -11,37 +11,41 @@ namespace llarp { template < typename Config, typename Section > - static const Section & + Section find_section(Config &c, const std::string &name, const Section &fallback) { - if(c.sections.find(name) == c.sections.end()) + Section ret; + if(c.VisitSection(name.c_str(), + [&ret](const ConfigParser::Section_t &s) -> bool { + for(const auto &item : s) + { + ret.emplace_back(item.first, item.second); + } + return true; + })) + return ret; + else return fallback; - return c.sections[name].values; } bool Config::Load(const char *fname) { - std::ifstream f; - f.open(fname); - if(f.is_open()) - { - ini::Parser parser(f); - auto &top = parser.top(); - router = find_section(top, "router", section_t{}); - network = find_section(top, "network", section_t{}); - connect = find_section(top, "connect", section_t{}); - netdb = find_section(top, "netdb", section_t{}); - dns = find_section(top, "dns", section_t{}); - iwp_links = find_section(top, "bind", section_t{}); - services = find_section(top, "services", section_t{}); - system = find_section(top, "system", section_t{}); - api = find_section(top, "api", section_t{}); - lokid = find_section(top, "lokid", section_t{}); - bootstrap = find_section(top, "bootstrap", section_t{}); - return true; - } - return false; + ConfigParser parser; + if(!parser.LoadFile(fname)) + return false; + router = find_section(parser, "router", section_t{}); + network = find_section(parser, "network", section_t{}); + connect = find_section(parser, "connect", section_t{}); + netdb = find_section(parser, "netdb", section_t{}); + dns = find_section(parser, "dns", section_t{}); + iwp_links = find_section(parser, "bind", section_t{}); + services = find_section(parser, "services", section_t{}); + system = find_section(parser, "system", section_t{}); + api = find_section(parser, "api", section_t{}); + lokid = find_section(parser, "lokid", section_t{}); + bootstrap = find_section(parser, "bootstrap", section_t{}); + return true; }; } // namespace llarp @@ -357,20 +361,23 @@ extern "C" llarp_config_iter(struct llarp_config *conf, struct llarp_config_iterator *iter) { - iter->conf = conf; - std::map< std::string, llarp::Config::section_t & > sections = { - {"network", conf->impl.network}, {"connect", conf->impl.connect}, - {"bootstrap", conf->impl.bootstrap}, {"system", conf->impl.system}, - {"netdb", conf->impl.netdb}, {"api", conf->impl.api}, - {"services", conf->impl.services}}; + iter->conf = conf; + std::unordered_map< std::string, const llarp::Config::section_t & > + sections = {{"network", conf->impl.network}, + {"connect", conf->impl.connect}, + {"bootstrap", conf->impl.bootstrap}, + {"system", conf->impl.system}, + {"netdb", conf->impl.netdb}, + {"api", conf->impl.api}, + {"services", conf->impl.services}}; - for(const auto item : conf->impl.router) + for(const auto &item : conf->impl.router) iter->visit(iter, "router", item.first.c_str(), item.second.c_str()); - for(const auto item : conf->impl.dns) + for(const auto &item : conf->impl.dns) iter->visit(iter, "dns", item.first.c_str(), item.second.c_str()); - for(const auto item : conf->impl.iwp_links) + for(const auto &item : conf->impl.iwp_links) iter->visit(iter, "bind", item.first.c_str(), item.second.c_str()); for(const auto §ion : sections) diff --git a/llarp/context.cpp b/llarp/context.cpp index b00db60db..4af4d6897 100644 --- a/llarp/context.cpp +++ b/llarp/context.cpp @@ -88,8 +88,8 @@ namespace llarp } } - void - Context::SetPIDFile(const std::string & fname) + void + Context::SetPIDFile(const std::string &fname) { pidfile = fname; } @@ -192,7 +192,7 @@ namespace llarp // run if(!router->Run(nodedb)) return 1; - + // run net io thread llarp::LogInfo("running mainloop"); llarp_ev_loop_run_single_process(mainloop, worker, logic); diff --git a/llarp/service/config.cpp b/llarp/service/config.cpp index 86e475de2..4c0468671 100644 --- a/llarp/service/config.cpp +++ b/llarp/service/config.cpp @@ -9,11 +9,17 @@ namespace llarp bool Config::Load(const std::string& fname) { - ini::Parser parser(fname); - for(const auto& sec : parser.top().ordered_sections) - { - services.push_back({sec->first, sec->second.values}); - } + llarp::ConfigParser parser; + if(!parser.LoadFile(fname.c_str())) + return false; + parser.IterAll([&](const llarp::ConfigParser::String_t& name, + const llarp::ConfigParser::Section_t& section) { + llarp::service::Config::section_t values; + values.first = name; + for(const auto& item : section) + values.second.emplace_back(item.first, item.second); + services.emplace_back(values); + }); return services.size() > 0; } diff --git a/llarp/util/ini.cpp b/llarp/util/ini.cpp index f3f4021b4..bf2ec8ecd 100644 --- a/llarp/util/ini.cpp +++ b/llarp/util/ini.cpp @@ -1 +1,140 @@ #include +#include +#include +#include + +namespace llarp +{ + bool + ConfigParser::LoadFile(const char* fname) + { + { + std::ifstream f(fname); + if(!f.is_open()) + return false; + f.seekg(0, std::ios::end); + m_Data.resize(f.tellg()); + f.seekg(0, std::ios::beg); + if(m_Data.size() == 0) + return false; + f.read(m_Data.data(), m_Data.size()); + } + return Parse(); + } + + bool + ConfigParser::LoadString(const std::string& str) + { + m_Data.resize(str.size()); + std::copy(str.begin(), str.end(), m_Data.begin()); + return Parse(); + } + + void + ConfigParser::Clear() + { + m_Config.clear(); + m_Data.clear(); + } + + bool + ConfigParser::Parse() + { + std::list< String_t > lines; + { + auto itr = m_Data.begin(); + // split into lines + while(itr != m_Data.end()) + { + auto beg = itr; + while(itr != m_Data.end() && *itr != '\n' && *itr != '\r') + ++itr; + lines.emplace_back(std::addressof(*beg), (itr - beg)); + if(itr == m_Data.end()) + break; + ++itr; + } + } + + String_t sectName; + + for(const auto& line : lines) + { + String_t realLine; + auto comment = line.find_first_of(';'); + if(comment == String_t::npos) + comment = line.find_first_of('#'); + if(comment == String_t::npos) + realLine = line; + else + realLine = line.substr(0, comment); + // blank or commented line? + if(realLine.size() == 0) + continue; + // find delimiters + auto sectOpenPos = realLine.find_first_of('['); + auto sectClosPos = realLine.find_first_of(']'); + auto kvDelim = realLine.find_first_of('='); + if(sectOpenPos != String_t::npos && sectClosPos != String_t::npos + && kvDelim == String_t::npos) + { + // section header + + // clamp whitespaces + ++sectOpenPos; + while(std::isspace(realLine[sectOpenPos]) && sectOpenPos != sectClosPos) + ++sectOpenPos; + --sectClosPos; + while(std::isspace(realLine[sectClosPos]) && sectClosPos != sectOpenPos) + --sectClosPos; + // set section name + sectName = realLine.substr(sectOpenPos, sectClosPos); + } + else if(kvDelim != String_t::npos) + { + // key value pair + String_t::size_type k_start = 0; + String_t::size_type k_end = kvDelim; + String_t::size_type v_start = kvDelim + 1; + String_t::size_type v_end = realLine.size() - 1; + // clamp whitespaces + while(std::isspace(realLine[k_start]) && k_start != kvDelim) + ++k_start; + while(std::isspace(realLine[k_end]) && k_end != k_start) + --k_end; + while(std::isspace(realLine[v_start]) && v_start != v_end) + ++v_start; + while(std::isspace(realLine[v_end])) + --v_end; + + // sect.k = v + String_t k = realLine.substr(k_start, k_end); + String_t v = realLine.substr(v_start, v_end); + Section_t& sect = m_Config[sectName]; + sect.emplace(k, v); + } + else // malformed? + return false; + } + return true; + } + + void + ConfigParser::IterAll( + std::function< void(const String_t&, const Section_t&) > visit) + { + for(const auto& item : m_Config) + visit(item.first, item.second); + } + + bool + ConfigParser::VisitSection( + const char* name, + std::function< bool(const Section_t& sect) > visit) const + { + auto itr = m_Config.find(name); + if(itr == m_Config.end()) + return false; + return visit(itr->second); + } +} // namespace llarp diff --git a/llarp/util/ini.hpp b/llarp/util/ini.hpp index 94b4bd09d..46f5b5b50 100644 --- a/llarp/util/ini.hpp +++ b/llarp/util/ini.hpp @@ -1,446 +1,52 @@ -/** - * The MIT License (MIT) - * Copyright (c) <2015> - * Copyright (c) <2018> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ - -#ifndef INI_HPP -#define INI_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include +#ifndef LOKINET_BOOTSERV_CONFIG_HPP +#define LOKINET_BOOTSERV_CONFIG_HPP +#include +#include +#include +#include #include -namespace ini +namespace llarp { - struct Level + struct ConfigParser { - Level() : parent(nullptr), depth(0) - { - } - Level(Level *p) : parent(p), depth(0) - { - } - - using value_map_t = std::list< std::pair< std::string, std::string > >; - using section_map_t = std::map< std::string, Level >; - using sections_t = std::list< section_map_t::const_iterator >; - value_map_t values; - section_map_t sections; - sections_t ordered_sections; - Level *parent; - size_t depth; + using String_t = llarp::string_view; + using Section_t = std::unordered_multimap< String_t, String_t >; + using Config_impl_t = std::unordered_map< String_t, Section_t >; + /// clear parser + void + Clear(); - static std::string default_value; + /// load config file for bootserv + /// return true on success + /// return false on error + bool + LoadFile(const char* fname); - const std::string &operator[](const std::string &name) - { - for(const auto &itr : values) - if(itr.first == name) - return itr.second; - return default_value; - } - Level & - operator()(const std::string &name) - { - return sections[name]; - } - }; + /// load from string + /// return true on success + /// return false on error + bool + LoadString(const std::string& str); - class Parser - { - public: - Parser(const std::string &fname) : Parser(fname.c_str()) - { - } - Parser(const char *fn); - Parser(std::istream &f) : f_(&f), ln_(0) - { - parse(top_); - } - Level & - top() - { - return top_; - } - void - dump(std::ostream &s) - { - dump(s, top(), ""); - } + /// iterate all sections and thier values void - print() - { - dump(std::cout, top(), ""); - } + IterAll(std::function< void(const String_t&, const Section_t&) > visit); + /// visit a section in config read only by name + /// return false if no section or value propagated from visitor bool - write(const std::string filename) - { - // this->print(); - // printf("parser::Write\n"); - std::ofstream s; - s.open(filename); - if(!s.is_open()) - { - printf("parser::Write - can't open\n"); - err("Cant open"); - return false; - } - // reset read - // printf("parser::Write - committing\n"); - this->commitStream(s); - s.close(); - return true; - } - - void - commitStream(std::ostream &s) - { - // printf("parser::commitStream - seekd\n"); - this->ln_ = 0; - this->f_->clear(); - this->f_->seekg(0); - // printf("parser::commitStream - reading top\n"); - std::vector< std::string > savedSections; - this->commit(s, top_, savedSections, false); - } + VisitSection(const char* name, + std::function< bool(const Section_t&) > visit) const; private: - void - dump(std::ostream &s, const Level &l, const std::string &sname); - void - parse(Level &l); - void - commit(std::ostream &s, Level &l, std::vector< std::string > &savedSections, - bool disabledSection); - void - parseSLine(std::string &sname, size_t &depth); - void - err(const char *s); + bool + Parse(); - private: - Level top_; - std::ifstream f0_; - std::istream *f_; - std::string line_; - size_t ln_; + std::vector< char > m_Data; + Config_impl_t m_Config; }; - inline void - Parser::err(const char *s) - { - char buf[256]; - sprintf(buf, "%s on line #%zu", s, ln_); - } - - inline std::string - trim(const std::string &s) - { - char p[] = " \t\r\n"; - long sp = 0; - long ep = s.length() - 1; - for(; sp <= ep; ++sp) - if(!strchr(p, s[sp])) - break; - for(; ep >= 0; --ep) - if(!strchr(p, s[ep])) - break; - return s.substr(sp, ep - sp + 1); - } - - inline Parser::Parser(const char *fn) : f0_(fn), f_(&f0_), ln_(0) - { - if(f0_) - { - parse(top_); - } - } - - inline void - Parser::parseSLine(std::string &sname, size_t &depth) - { - depth = 0; - for(; depth < line_.length(); ++depth) - if(line_[depth] != '[') - break; - - sname = line_.substr(depth, line_.length() - 2 * depth); - } - - inline void - Parser::parse(Level &l) - { - while(std::getline(*f_, line_)) - { - ++ln_; - if(line_[0] == '#' || line_[0] == ';') - continue; - line_ = trim(line_); - if(line_.empty()) - continue; - if(line_[0] == '[') - { - size_t depth; - std::string sname; - parseSLine(sname, depth); - Level *lp = nullptr; - Level *parent = &l; - if(depth > l.depth + 1) - err("section with wrong depth"); - if(l.depth == depth - 1) - lp = &l.sections[sname]; - else - { - lp = l.parent; - size_t n = l.depth - depth; - for(size_t i = 0; i < n; ++i) - lp = lp->parent; - parent = lp; - lp = &lp->sections[sname]; - } - if(lp->depth != 0) - err("duplicate section name on the same level"); - if(!lp->parent) - { - lp->depth = depth; - lp->parent = parent; - } - parent->ordered_sections.push_back(parent->sections.find(sname)); - parse(*lp); - } - else - { - size_t n = line_.find('='); - if(n == std::string::npos) - err("no '=' found"); - - auto p = - std::make_pair(trim(line_.substr(0, n)), - trim(line_.substr(n + 1, line_.length() - n - 1))); - l.values.push_back(p); - } - } - } - - inline void - saveValues(std::ostream &s, std::vector< std::string > excludes, Level &l) - { - // printf("checking keys[%lu] against [%lu]\n", l.values.size(), - // excludes.size()); - for(auto it = l.values.begin(); it != l.values.end(); ++it) - { - // printf("key[%s]\n", it->first.c_str()); - auto check = find(excludes.begin(), excludes.end(), it->first); - if(check == excludes.end()) - { - // printf("We didnt write it [%s=%s]\n", it->first.c_str(), - // it->second.c_str()); - s << it->first + "=" + it->second << "\n"; // commit to stream - } - } - } - - inline void - Parser::commit(std::ostream &s, Level &l, - std::vector< std::string > &savedSections, - bool disabledSection) - { - std::vector< std::string > keys; - bool keysChecked = false; - while(std::getline(*this->f_, line_)) - { - ++ln_; - if(line_[0] == '#' || line_[0] == ';') - { - s << line_ << "\n"; // commit to stream - continue; - } - std::string tline_ = trim(line_); - if(tline_.empty()) - { - s << line_ << "\n"; // commit to stream - continue; - } - if(tline_[0] == '[') - { - bool disableNextSection = false; - size_t depth; - std::string sname; - parseSLine(sname, depth); - s << "[" << sname << "]" - << "\n"; // commit to stream - - auto test = this->top_.sections.find(sname); - if(test == this->top_.sections.end()) - { - // could mean we're done with this section - // printf("We dont have section [%s]\n", sname.c_str()); - // we'll comment out these keys since we've intentionally dropped them - disableNextSection = true; - } - - Level *lp = NULL; - Level *parent = &l; - if(depth > l.depth + 1) - err("section with wrong depth"); - - // if depth is one level deep - if(l.depth == depth - 1) - { - // make level point to one of our sections - lp = &l.sections[sname]; - } - else - { - // find the parent by depth - lp = l.parent; - size_t n = l.depth - depth; - for(size_t i = 0; i < n; ++i) - lp = lp->parent; - parent = lp; - lp = &lp->sections[sname]; - } - /* - if(lp->depth != 0) - { - printf("has depth still, found [%s] at [%zu]\n", sname.c_str(), - depth); - } - */ - if(!lp->parent) - { - printf("no parent\n"); - lp->depth = depth; - lp->parent = parent; - } - - // flush remainder of this section - saveValues(s, keys, l); - keysChecked = true; - - // start next section - this->commit(s, *lp, savedSections, disableNextSection); - savedSections.push_back(sname); - } - else - { - size_t n = line_.find('='); - if(n == std::string::npos) - err("no '=' found"); - - auto key = trim(line_.substr(0, n)); - keys.push_back(key); - auto val = std::find_if( - l.values.begin(), l.values.end(), - [&key](const std::pair< std::string, std::string > &element) { - return element.first == key; - }); - if(val != l.values.end()) - { - if(val->second.c_str() - == trim(line_.substr(n + 1, line_.length() - n - 1))) - { - // copying line - if(disabledSection) - s << "# "; - s << line_ << "\n"; // commit to stream - } - else - { - // update value - if(disabledSection) - s << "# "; - s << line_.substr(0, n) + "=" + val->second - << "\n"; // commit to stream - } - } /* - else - { - // remove it - //printf("kv found [%s] no current\n", key.c_str()); - } */ - } - } - - // handle last section - if(!keysChecked) - { - saveValues(s, keys, l); - } - - // we're at the main level and have the list of sections - if(l.sections.size()) - { - // check to make sure we've written out all the sections we need to - // printf("sections old[%lu] run[%lu]\n", savedSections.size(), - // l.sections.size()); - for(auto it = l.sections.begin(); it != l.sections.end(); ++it) - { - // printf("sections[%s]\n", it->first.c_str()); - auto check = - find(savedSections.begin(), savedSections.end(), it->first); - if(check == savedSections.end()) - { - // printf("Adding section [%s]\n", it->first.c_str()); - // s << "[" << it->first + "]" << "\n"; // commit to stream - dump(s, l.sections[it->first], it->first); - } - } - } - } - - inline void - Parser::dump(std::ostream &s, const Level &l, const std::string &sname) - { - if(!sname.empty()) - s << '\n'; - for(size_t i = 0; i < l.depth; ++i) - s << '['; - if(!sname.empty()) - s << sname; - for(size_t i = 0; i < l.depth; ++i) - s << ']'; - if(!sname.empty()) - s << std::endl; - - for(const auto &itr : l.values) - s << itr.first << '=' << itr.second << std::endl; - - for(Level::sections_t::const_iterator it = l.ordered_sections.begin(); - it != l.ordered_sections.end(); ++it) - { - assert((*it)->second.depth == l.depth + 1); - dump(s, (*it)->second, (*it)->first); - } - } - -} // namespace ini +} // namespace llarp -#endif // INI_HPP +#endif diff --git a/test/util/test_llarp_util_ini.cpp b/test/util/test_llarp_util_ini.cpp new file mode 100644 index 000000000..7384fd3d0 --- /dev/null +++ b/test/util/test_llarp_util_ini.cpp @@ -0,0 +1,51 @@ +#include + +#include + +struct TestINIParser : public ::testing::Test +{ + llarp::ConfigParser parser; + + void TearDown() + { + parser.Clear(); + } +}; + +TEST_F(TestINIParser, TestParseEmpty) +{ + ASSERT_TRUE(parser.LoadString("")); +} + +TEST_F(TestINIParser, TestParseOneSection) +{ + llarp::ConfigParser::Section_t sect; + auto assertVisit = [§](const auto & section) -> bool { + sect = section; + return true; + }; + ASSERT_TRUE(parser.LoadString("[test]\nkey=val")); + ASSERT_TRUE(parser.VisitSection("test", assertVisit)); + auto itr = sect.find("notfound"); + ASSERT_EQ(itr, sect.end()); + itr = sect.find("key"); + ASSERT_NE(itr, sect.end()); + ASSERT_STREQ(itr->second.c_str(), "val"); +} + +TEST_F(TestINIParser, TestParseSectionDuplicateKeys) +{ + ASSERT_TRUE(parser.LoadString("[test]\nkey1=val1\nkey1=val2")); + size_t num = 0; + auto visit =[&num](const auto & section) -> bool { + num = section.count("key1"); + return true; + }; + ASSERT_TRUE(parser.VisitSection("test", visit)); + ASSERT_EQ(num, size_t(2)); +} + +TEST_F(TestINIParser, TestParseInvalid) +{ + ASSERT_FALSE(parser.LoadString("srged5ghe5\nf34wtge5\nw34tgfs4ygsd5yg=4;\n#g4syhgd5\n")); +} \ No newline at end of file