blob: afa563ac0c197dd1d6628387ccf444072773798d [file] [log] [blame]
#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