| #include "config_parser.hpp" |
| |
| #include <fmt/compile.h> |
| #include <fmt/format.h> |
| |
| #include <stdplus/exception.hpp> |
| #include <stdplus/fd/atomic.hpp> |
| #include <stdplus/fd/create.hpp> |
| #include <stdplus/fd/fmt.hpp> |
| #include <stdplus/fd/line.hpp> |
| |
| #include <functional> |
| #include <iterator> |
| #include <stdexcept> |
| #include <string> |
| #include <utility> |
| |
| namespace phosphor |
| { |
| namespace network |
| { |
| namespace config |
| { |
| |
| using std::literals::string_view_literals::operator""sv; |
| |
| bool icaseeq(std::string_view in, std::string_view expected) noexcept |
| { |
| return std::equal(in.begin(), in.end(), expected.begin(), expected.end(), |
| [](auto a, auto b) { return tolower(a) == b; }); |
| } |
| |
| std::optional<bool> parseBool(std::string_view in) noexcept |
| { |
| if (in == "1"sv || icaseeq(in, "yes"sv) || icaseeq(in, "y"sv) || |
| icaseeq(in, "true"sv) || icaseeq(in, "t"sv) || icaseeq(in, "on"sv)) |
| { |
| return true; |
| } |
| if (in == "0"sv || icaseeq(in, "no"sv) || icaseeq(in, "n"sv) || |
| icaseeq(in, "false"sv) || icaseeq(in, "f"sv) || icaseeq(in, "off"sv)) |
| { |
| return false; |
| } |
| return std::nullopt; |
| } |
| |
| fs::path pathForIntfConf(const fs::path& dir, std::string_view intf) |
| { |
| return dir / fmt::format(FMT_COMPILE("00-bmc-{}.network"), intf); |
| } |
| |
| fs::path pathForIntfDev(const fs::path& dir, std::string_view intf) |
| { |
| return dir / fmt::format(FMT_COMPILE("{}.netdev"), intf); |
| } |
| |
| const std::string* |
| SectionMap::getLastValueString(std::string_view section, |
| std::string_view key) const noexcept |
| { |
| auto sit = find(section); |
| if (sit == end()) |
| { |
| return nullptr; |
| } |
| for (auto it = sit->second.rbegin(); it != sit->second.rend(); ++it) |
| { |
| auto kit = it->find(key); |
| if (kit == it->end() || kit->second.empty()) |
| { |
| continue; |
| } |
| return &kit->second.back().get(); |
| } |
| return nullptr; |
| } |
| |
| std::vector<std::string> SectionMap::getValueStrings(std::string_view section, |
| std::string_view key) const |
| { |
| return getValues(section, key, |
| [](const Value& v) { return std::string(v); }); |
| } |
| |
| void KeyCheck::operator()(std::string_view s) |
| { |
| for (auto c : s) |
| { |
| if (c == '\n' || c == '=') |
| { |
| throw std::invalid_argument( |
| fmt::format(FMT_COMPILE("Invalid Config Key: {}"), s)); |
| } |
| } |
| } |
| |
| void SectionCheck::operator()(std::string_view s) |
| { |
| for (auto c : s) |
| { |
| if (c == '\n' || c == ']') |
| { |
| throw std::invalid_argument( |
| fmt::format(FMT_COMPILE("Invalid Config Section: {}"), s)); |
| } |
| } |
| } |
| |
| void ValueCheck::operator()(std::string_view s) |
| { |
| for (auto c : s) |
| { |
| if (c == '\n') |
| { |
| throw std::invalid_argument( |
| fmt::format(FMT_COMPILE("Invalid Config Value: {}"), s)); |
| } |
| } |
| } |
| |
| Parser::Parser(const fs::path& filename) |
| { |
| setFile(filename); |
| } |
| |
| constexpr bool isspace(char c) noexcept |
| { |
| return c == ' ' || c == '\t'; |
| } |
| |
| constexpr bool iscomment(char c) noexcept |
| { |
| return c == '#' || c == ';'; |
| } |
| |
| static void removePadding(std::string_view& str) noexcept |
| { |
| size_t idx = str.size(); |
| for (; idx > 0 && isspace(str[idx - 1]); idx--) |
| ; |
| str.remove_suffix(str.size() - idx); |
| |
| idx = 0; |
| for (; idx < str.size() && isspace(str[idx]); idx++) |
| ; |
| str.remove_prefix(idx); |
| } |
| |
| struct Parse |
| { |
| std::reference_wrapper<const fs::path> filename; |
| SectionMap map; |
| KeyValuesMap* section; |
| std::vector<std::string> warnings; |
| size_t lineno; |
| |
| inline Parse(const fs::path& filename) : |
| filename(filename), section(nullptr), lineno(0) |
| {} |
| |
| void pumpSection(std::string_view line) |
| { |
| auto cpos = line.find(']'); |
| if (cpos == line.npos) |
| { |
| warnings.emplace_back(fmt::format("{}:{}: Section missing ]", |
| filename.get().native(), lineno)); |
| } |
| else |
| { |
| for (auto c : line.substr(cpos + 1)) |
| { |
| if (!isspace(c)) |
| { |
| warnings.emplace_back( |
| fmt::format("{}:{}: Characters outside section name", |
| filename.get().native(), lineno)); |
| break; |
| } |
| } |
| } |
| auto s = line.substr(0, cpos); |
| auto it = map.find(s); |
| if (it == map.end()) |
| { |
| std::tie(it, std::ignore) = map.emplace( |
| Section(Section::unchecked(), s), KeyValuesMapList{}); |
| } |
| section = &it->second.emplace_back(); |
| } |
| |
| void pumpKV(std::string_view line) |
| { |
| auto epos = line.find('='); |
| std::vector<std::string> new_warnings; |
| if (epos == line.npos) |
| { |
| new_warnings.emplace_back(fmt::format( |
| "{}:{}: KV missing `=`", filename.get().native(), lineno)); |
| } |
| auto k = line.substr(0, epos); |
| removePadding(k); |
| if (section == nullptr) |
| { |
| new_warnings.emplace_back( |
| fmt::format("{}:{}: Key `{}` missing section", |
| filename.get().native(), lineno, k)); |
| } |
| if (!new_warnings.empty()) |
| { |
| warnings.insert(warnings.end(), |
| std::make_move_iterator(new_warnings.begin()), |
| std::make_move_iterator(new_warnings.end())); |
| return; |
| } |
| auto v = line.substr(epos + 1); |
| removePadding(v); |
| |
| auto it = section->find(k); |
| if (it == section->end()) |
| { |
| std::tie(it, std::ignore) = |
| section->emplace(Key(Key::unchecked(), k), ValueList{}); |
| } |
| it->second.emplace_back(Value::unchecked(), v); |
| } |
| |
| void pump(std::string_view line) |
| { |
| lineno++; |
| for (size_t i = 0; i < line.size(); ++i) |
| { |
| auto c = line[i]; |
| if (iscomment(c)) |
| { |
| return; |
| } |
| else if (c == '[') |
| { |
| return pumpSection(line.substr(i + 1)); |
| } |
| else if (!isspace(c)) |
| { |
| return pumpKV(line.substr(i)); |
| } |
| } |
| } |
| }; |
| |
| void Parser::setFile(const fs::path& filename) |
| { |
| Parse parse(filename); |
| |
| bool fileExists = true; |
| try |
| { |
| auto fd = stdplus::fd::open(filename.c_str(), |
| stdplus::fd::OpenAccess::ReadOnly); |
| stdplus::fd::LineReader reader(fd); |
| while (true) |
| { |
| parse.pump(*reader.readLine()); |
| } |
| } |
| catch (const stdplus::exception::Eof&) |
| {} |
| catch (const std::system_error& e) |
| { |
| fileExists = false; |
| // TODO: Pass exceptions once callers can handle them |
| parse.warnings.emplace_back( |
| fmt::format("{}: Open error: {}", filename.native(), e.what())); |
| } |
| |
| this->map = std::move(parse.map); |
| this->fileExists = fileExists; |
| this->filename = filename; |
| this->warnings = std::move(parse.warnings); |
| } |
| |
| static void writeFileInt(const SectionMap& map, const fs::path& filename) |
| { |
| stdplus::fd::AtomicWriter writer(filename, 0644); |
| stdplus::fd::FormatBuffer out(writer); |
| for (const auto& [section, maps] : map) |
| { |
| for (const auto& map : maps) |
| { |
| out.append(FMT_COMPILE("[{}]\n"), section.get()); |
| for (const auto& [key, vals] : map) |
| { |
| for (const auto& val : vals) |
| { |
| out.append(FMT_COMPILE("{}={}\n"), key.get(), val.get()); |
| } |
| } |
| } |
| } |
| out.flush(); |
| writer.commit(); |
| } |
| |
| void Parser::writeFile() const |
| { |
| writeFileInt(map, filename); |
| } |
| |
| void Parser::writeFile(const fs::path& filename) |
| { |
| writeFileInt(map, filename); |
| this->filename = filename; |
| } |
| |
| } // namespace config |
| } // namespace network |
| } // namespace phosphor |