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* SectionMap::getLastValueString( 58 std::string_view section, std::string_view key) const noexcept 59 { 60 auto sit = find(section); 61 if (sit == end()) 62 { 63 return nullptr; 64 } 65 for (auto it = sit->second.rbegin(); it != sit->second.rend(); ++it) 66 { 67 auto kit = it->find(key); 68 if (kit == it->end() || kit->second.empty()) 69 { 70 continue; 71 } 72 return &kit->second.back().get(); 73 } 74 return nullptr; 75 } 76 77 std::vector<std::string> SectionMap::getValueStrings(std::string_view section, 78 std::string_view key) const 79 { 80 return getValues(section, key, 81 [](const Value& v) { return std::string(v); }); 82 } 83 84 void KeyCheck::operator()(std::string_view s) 85 { 86 for (auto c : s) 87 { 88 if (c == '\n' || c == '=') 89 { 90 throw std::invalid_argument( 91 stdplus::strCat("Invalid Config Key: "sv, s)); 92 } 93 } 94 } 95 96 void SectionCheck::operator()(std::string_view s) 97 { 98 for (auto c : s) 99 { 100 if (c == '\n' || c == ']') 101 { 102 throw std::invalid_argument( 103 stdplus::strCat("Invalid Config Section: "sv, s)); 104 } 105 } 106 } 107 108 void ValueCheck::operator()(std::string_view s) 109 { 110 for (auto c : s) 111 { 112 if (c == '\n') 113 { 114 throw std::invalid_argument( 115 stdplus::strCat("Invalid Config Value: "sv, s)); 116 } 117 } 118 } 119 120 Parser::Parser(const fs::path& filename) 121 { 122 setFile(filename); 123 } 124 125 constexpr bool isspace(char c) noexcept 126 { 127 return c == ' ' || c == '\t'; 128 } 129 130 constexpr bool iscomment(char c) noexcept 131 { 132 return c == '#' || c == ';'; 133 } 134 135 static void removePadding(std::string_view& str) noexcept 136 { 137 size_t idx = str.size(); 138 for (; idx > 0 && isspace(str[idx - 1]); idx--) 139 ; 140 str.remove_suffix(str.size() - idx); 141 142 idx = 0; 143 for (; idx < str.size() && isspace(str[idx]); idx++) 144 ; 145 str.remove_prefix(idx); 146 } 147 148 struct Parse 149 { 150 std::reference_wrapper<const fs::path> filename; 151 SectionMap map; 152 KeyValuesMap* section; 153 std::vector<std::string> warnings; 154 size_t lineno; 155 156 inline Parse(const fs::path& filename) : 157 filename(filename), section(nullptr), lineno(0) 158 {} 159 160 void pumpSection(std::string_view line) 161 { 162 auto cpos = line.find(']'); 163 if (cpos == line.npos) 164 { 165 warnings.emplace_back(std::format("{}:{}: Section missing ]", 166 filename.get().native(), lineno)); 167 } 168 else 169 { 170 for (auto c : line.substr(cpos + 1)) 171 { 172 if (!isspace(c)) 173 { 174 warnings.emplace_back( 175 std::format("{}:{}: Characters outside section name", 176 filename.get().native(), lineno)); 177 break; 178 } 179 } 180 } 181 auto s = line.substr(0, cpos); 182 auto it = map.find(s); 183 if (it == map.end()) 184 { 185 std::tie(it, std::ignore) = map.emplace( 186 Section(Section::unchecked(), s), KeyValuesMapList{}); 187 } 188 section = &it->second.emplace_back(); 189 } 190 191 void pumpKV(std::string_view line) 192 { 193 auto epos = line.find('='); 194 std::vector<std::string> new_warnings; 195 if (epos == line.npos) 196 { 197 new_warnings.emplace_back(std::format( 198 "{}:{}: KV missing `=`", filename.get().native(), lineno)); 199 } 200 auto k = line.substr(0, epos); 201 removePadding(k); 202 if (section == nullptr) 203 { 204 new_warnings.emplace_back( 205 std::format("{}:{}: Key `{}` missing section", 206 filename.get().native(), lineno, k)); 207 } 208 if (!new_warnings.empty()) 209 { 210 warnings.insert(warnings.end(), 211 std::make_move_iterator(new_warnings.begin()), 212 std::make_move_iterator(new_warnings.end())); 213 return; 214 } 215 auto v = line.substr(epos + 1); 216 removePadding(v); 217 218 auto it = section->find(k); 219 if (it == section->end()) 220 { 221 std::tie(it, std::ignore) = 222 section->emplace(Key(Key::unchecked(), k), ValueList{}); 223 } 224 it->second.emplace_back(Value::unchecked(), v); 225 } 226 227 void pump(std::string_view line) 228 { 229 lineno++; 230 for (size_t i = 0; i < line.size(); ++i) 231 { 232 auto c = line[i]; 233 if (iscomment(c)) 234 { 235 return; 236 } 237 else if (c == '[') 238 { 239 return pumpSection(line.substr(i + 1)); 240 } 241 else if (!isspace(c)) 242 { 243 return pumpKV(line.substr(i)); 244 } 245 } 246 } 247 }; 248 249 void Parser::setFile(const fs::path& filename) 250 { 251 Parse parse(filename); 252 253 bool fileExists = true; 254 try 255 { 256 auto fd = stdplus::fd::open(filename.c_str(), 257 stdplus::fd::OpenAccess::ReadOnly); 258 stdplus::fd::LineReader reader(fd); 259 while (true) 260 { 261 parse.pump(*reader.readLine()); 262 } 263 } 264 catch (const stdplus::exception::Eof&) 265 {} 266 catch (const std::system_error& e) 267 { 268 fileExists = false; 269 // TODO: Pass exceptions once callers can handle them 270 parse.warnings.emplace_back( 271 std::format("{}: Open error: {}", filename.native(), e.what())); 272 } 273 274 this->map = std::move(parse.map); 275 this->fileExists = fileExists; 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.appends("["sv, section.get(), "]\n"sv); 289 for (const auto& [key, vals] : map) 290 { 291 for (const auto& val : vals) 292 { 293 out.appends(key.get(), "="sv, val.get(), "\n"sv); 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