1 #include "config_parser.hpp" 2 3 #include <stdplus/exception.hpp> 4 #include <stdplus/fd/atomic.hpp> 5 #include <stdplus/fd/create.hpp> 6 #include <stdplus/fd/fmt.hpp> 7 #include <stdplus/fd/line.hpp> 8 #include <stdplus/str/cat.hpp> 9 10 #include <format> 11 #include <functional> 12 #include <iterator> 13 #include <stdexcept> 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 / stdplus::strCat("00-bmc-"sv, intf, ".network"sv); 50 } 51 52 fs::path pathForIntfDev(const fs::path& dir, std::string_view intf) 53 { 54 return dir / stdplus::strCat(intf, ".netdev"sv); 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 stdplus::strCat("Invalid Config Key: "sv, 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 stdplus::strCat("Invalid Config Section: "sv, 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 stdplus::strCat("Invalid Config Value: "sv, 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 void pumpSection(std::string_view line) 162 { 163 auto cpos = line.find(']'); 164 if (cpos == line.npos) 165 { 166 warnings.emplace_back(std::format("{}:{}: Section missing ]", 167 filename.get().native(), lineno)); 168 } 169 else 170 { 171 for (auto c : line.substr(cpos + 1)) 172 { 173 if (!isspace(c)) 174 { 175 warnings.emplace_back( 176 std::format("{}:{}: Characters outside section name", 177 filename.get().native(), lineno)); 178 break; 179 } 180 } 181 } 182 auto s = line.substr(0, cpos); 183 auto it = map.find(s); 184 if (it == map.end()) 185 { 186 std::tie(it, std::ignore) = map.emplace( 187 Section(Section::unchecked(), s), KeyValuesMapList{}); 188 } 189 section = &it->second.emplace_back(); 190 } 191 192 void pumpKV(std::string_view line) 193 { 194 auto epos = line.find('='); 195 std::vector<std::string> new_warnings; 196 if (epos == line.npos) 197 { 198 new_warnings.emplace_back(std::format( 199 "{}:{}: KV missing `=`", filename.get().native(), lineno)); 200 } 201 auto k = line.substr(0, epos); 202 removePadding(k); 203 if (section == nullptr) 204 { 205 new_warnings.emplace_back( 206 std::format("{}:{}: Key `{}` missing section", 207 filename.get().native(), lineno, k)); 208 } 209 if (!new_warnings.empty()) 210 { 211 warnings.insert(warnings.end(), 212 std::make_move_iterator(new_warnings.begin()), 213 std::make_move_iterator(new_warnings.end())); 214 return; 215 } 216 auto v = line.substr(epos + 1); 217 removePadding(v); 218 219 auto it = section->find(k); 220 if (it == section->end()) 221 { 222 std::tie(it, std::ignore) = 223 section->emplace(Key(Key::unchecked(), k), ValueList{}); 224 } 225 it->second.emplace_back(Value::unchecked(), v); 226 } 227 228 void pump(std::string_view line) 229 { 230 lineno++; 231 for (size_t i = 0; i < line.size(); ++i) 232 { 233 auto c = line[i]; 234 if (iscomment(c)) 235 { 236 return; 237 } 238 else if (c == '[') 239 { 240 return pumpSection(line.substr(i + 1)); 241 } 242 else if (!isspace(c)) 243 { 244 return pumpKV(line.substr(i)); 245 } 246 } 247 } 248 }; 249 250 void Parser::setFile(const fs::path& filename) 251 { 252 Parse parse(filename); 253 254 bool fileExists = true; 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 catch (const std::system_error& e) 268 { 269 fileExists = false; 270 // TODO: Pass exceptions once callers can handle them 271 parse.warnings.emplace_back( 272 std::format("{}: Open error: {}", filename.native(), e.what())); 273 } 274 275 this->map = std::move(parse.map); 276 this->fileExists = fileExists; 277 this->filename = filename; 278 this->warnings = std::move(parse.warnings); 279 } 280 281 static void writeFileInt(const SectionMap& map, const fs::path& filename) 282 { 283 stdplus::fd::AtomicWriter writer(filename, 0644); 284 stdplus::fd::FormatBuffer out(writer); 285 for (const auto& [section, maps] : map) 286 { 287 for (const auto& map : maps) 288 { 289 out.appends("["sv, section.get(), "]\n"sv); 290 for (const auto& [key, vals] : map) 291 { 292 for (const auto& val : vals) 293 { 294 out.appends(key.get(), "="sv, val.get(), "\n"sv); 295 } 296 } 297 } 298 } 299 out.flush(); 300 writer.commit(); 301 } 302 303 void Parser::writeFile() const 304 { 305 writeFileInt(map, filename); 306 } 307 308 void Parser::writeFile(const fs::path& filename) 309 { 310 writeFileInt(map, filename); 311 this->filename = filename; 312 } 313 314 } // namespace config 315 } // namespace network 316 } // namespace phosphor 317