1 #include "config_parser.hpp" 2 3 #include <fmt/compile.h> 4 #include <fmt/format.h> 5 6 #include <functional> 7 #include <iterator> 8 #include <stdexcept> 9 #include <stdplus/exception.hpp> 10 #include <stdplus/fd/atomic.hpp> 11 #include <stdplus/fd/create.hpp> 12 #include <stdplus/fd/fmt.hpp> 13 #include <stdplus/fd/line.hpp> 14 #include <string> 15 #include <utility> 16 17 namespace phosphor 18 { 19 namespace network 20 { 21 namespace config 22 { 23 24 using std::literals::string_view_literals::operator""sv; 25 26 bool icaseeq(std::string_view in, std::string_view expected) noexcept 27 { 28 return std::equal(in.begin(), in.end(), expected.begin(), expected.end(), 29 [](auto a, auto b) { return tolower(a) == b; }); 30 } 31 32 std::optional<bool> parseBool(std::string_view in) noexcept 33 { 34 if (in == "1"sv || icaseeq(in, "yes"sv) || icaseeq(in, "y"sv) || 35 icaseeq(in, "true"sv) || icaseeq(in, "t"sv) || icaseeq(in, "on"sv)) 36 { 37 return true; 38 } 39 if (in == "0"sv || icaseeq(in, "no"sv) || icaseeq(in, "n"sv) || 40 icaseeq(in, "false"sv) || icaseeq(in, "f"sv) || icaseeq(in, "off"sv)) 41 { 42 return false; 43 } 44 return std::nullopt; 45 } 46 47 fs::path pathForIntfConf(const fs::path& dir, std::string_view intf) 48 { 49 return dir / fmt::format(FMT_COMPILE("00-bmc-{}.network"), intf); 50 } 51 52 fs::path pathForIntfDev(const fs::path& dir, std::string_view intf) 53 { 54 return dir / fmt::format(FMT_COMPILE("{}.netdev"), intf); 55 } 56 57 const std::string* 58 SectionMap::getLastValueString(std::string_view section, 59 std::string_view key) const noexcept 60 { 61 auto sit = find(section); 62 if (sit == end()) 63 { 64 return nullptr; 65 } 66 for (auto it = sit->second.rbegin(); it != sit->second.rend(); ++it) 67 { 68 auto kit = it->find(key); 69 if (kit == it->end() || kit->second.empty()) 70 { 71 continue; 72 } 73 return &kit->second.back().get(); 74 } 75 return nullptr; 76 } 77 78 std::vector<std::string> SectionMap::getValueStrings(std::string_view section, 79 std::string_view key) const 80 { 81 return getValues(section, key, 82 [](const Value& v) { return std::string(v); }); 83 } 84 85 void KeyCheck::operator()(std::string_view s) 86 { 87 for (auto c : s) 88 { 89 if (c == '\n' || c == '=') 90 { 91 throw std::invalid_argument( 92 fmt::format(FMT_COMPILE("Invalid Config Key: {}"), s)); 93 } 94 } 95 } 96 97 void SectionCheck::operator()(std::string_view s) 98 { 99 for (auto c : s) 100 { 101 if (c == '\n' || c == ']') 102 { 103 throw std::invalid_argument( 104 fmt::format(FMT_COMPILE("Invalid Config Section: {}"), s)); 105 } 106 } 107 } 108 109 void ValueCheck::operator()(std::string_view s) 110 { 111 for (auto c : s) 112 { 113 if (c == '\n') 114 { 115 throw std::invalid_argument( 116 fmt::format(FMT_COMPILE("Invalid Config Value: {}"), s)); 117 } 118 } 119 } 120 121 Parser::Parser(const fs::path& filename) 122 { 123 setFile(filename); 124 } 125 126 constexpr bool isspace(char c) noexcept 127 { 128 return c == ' ' || c == '\t'; 129 } 130 131 constexpr bool iscomment(char c) noexcept 132 { 133 return c == '#' || c == ';'; 134 } 135 136 static void removePadding(std::string_view& str) noexcept 137 { 138 size_t idx = str.size(); 139 for (; idx > 0 && isspace(str[idx - 1]); idx--) 140 ; 141 str.remove_suffix(str.size() - idx); 142 143 idx = 0; 144 for (; idx < str.size() && isspace(str[idx]); idx++) 145 ; 146 str.remove_prefix(idx); 147 } 148 149 struct Parse 150 { 151 std::reference_wrapper<const fs::path> filename; 152 SectionMap map; 153 KeyValuesMap* section; 154 std::vector<std::string> warnings; 155 size_t lineno; 156 157 inline Parse(const fs::path& filename) : 158 filename(filename), section(nullptr), lineno(0) 159 { 160 } 161 162 void pumpSection(std::string_view line) 163 { 164 auto cpos = line.find(']'); 165 if (cpos == line.npos) 166 { 167 warnings.emplace_back(fmt::format("{}:{}: Section missing ]", 168 filename.get().native(), lineno)); 169 } 170 else 171 { 172 for (auto c : line.substr(cpos + 1)) 173 { 174 if (!isspace(c)) 175 { 176 warnings.emplace_back( 177 fmt::format("{}:{}: Characters outside section name", 178 filename.get().native(), lineno)); 179 break; 180 } 181 } 182 } 183 auto s = line.substr(0, cpos); 184 auto it = map.find(s); 185 if (it == map.end()) 186 { 187 std::tie(it, std::ignore) = map.emplace( 188 Section(Section::unchecked(), s), KeyValuesMapList{}); 189 } 190 section = &it->second.emplace_back(); 191 } 192 193 void pumpKV(std::string_view line) 194 { 195 auto epos = line.find('='); 196 std::vector<std::string> new_warnings; 197 if (epos == line.npos) 198 { 199 new_warnings.emplace_back(fmt::format( 200 "{}:{}: KV missing `=`", filename.get().native(), lineno)); 201 } 202 auto k = line.substr(0, epos); 203 removePadding(k); 204 if (section == nullptr) 205 { 206 new_warnings.emplace_back( 207 fmt::format("{}:{}: Key `{}` missing section", 208 filename.get().native(), lineno, k)); 209 } 210 if (!new_warnings.empty()) 211 { 212 warnings.insert(warnings.end(), 213 std::make_move_iterator(new_warnings.begin()), 214 std::make_move_iterator(new_warnings.end())); 215 return; 216 } 217 auto v = line.substr(epos + 1); 218 removePadding(v); 219 220 auto it = section->find(k); 221 if (it == section->end()) 222 { 223 std::tie(it, std::ignore) = 224 section->emplace(Key(Key::unchecked(), k), ValueList{}); 225 } 226 it->second.emplace_back(Value::unchecked(), v); 227 } 228 229 void pump(std::string_view line) 230 { 231 lineno++; 232 for (size_t i = 0; i < line.size(); ++i) 233 { 234 auto c = line[i]; 235 if (iscomment(c)) 236 { 237 return; 238 } 239 else if (c == '[') 240 { 241 return pumpSection(line.substr(i + 1)); 242 } 243 else if (!isspace(c)) 244 { 245 return pumpKV(line.substr(i)); 246 } 247 } 248 } 249 }; 250 251 void Parser::setFile(const fs::path& filename) 252 { 253 Parse parse(filename); 254 255 try 256 { 257 auto fd = stdplus::fd::open(filename.c_str(), 258 stdplus::fd::OpenAccess::ReadOnly); 259 stdplus::fd::LineReader reader(fd); 260 while (true) 261 { 262 parse.pump(*reader.readLine()); 263 } 264 } 265 catch (const stdplus::exception::Eof&) 266 { 267 } 268 catch (const std::exception& e) 269 { 270 // TODO: Pass exceptions once callers can handle them 271 parse.warnings.emplace_back( 272 fmt::format("{}: Read error: {}", filename.native(), e.what())); 273 } 274 275 this->map = std::move(parse.map); 276 this->filename = filename; 277 this->warnings = std::move(parse.warnings); 278 } 279 280 static void writeFileInt(const SectionMap& map, const fs::path& filename) 281 { 282 stdplus::fd::AtomicWriter writer(filename, 0644); 283 stdplus::fd::FormatBuffer out(writer); 284 for (const auto& [section, maps] : map) 285 { 286 for (const auto& map : maps) 287 { 288 out.append(FMT_COMPILE("[{}]\n"), section.get()); 289 for (const auto& [key, vals] : map) 290 { 291 for (const auto& val : vals) 292 { 293 out.append(FMT_COMPILE("{}={}\n"), key.get(), val.get()); 294 } 295 } 296 } 297 } 298 out.flush(); 299 writer.commit(); 300 } 301 302 void Parser::writeFile() const 303 { 304 writeFileInt(map, filename); 305 } 306 307 void Parser::writeFile(const fs::path& filename) 308 { 309 writeFileInt(map, filename); 310 this->filename = filename; 311 } 312 313 } // namespace config 314 } // namespace network 315 } // namespace phosphor 316