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 bool fileExists = true; 256 try 257 { 258 auto fd = stdplus::fd::open(filename.c_str(), 259 stdplus::fd::OpenAccess::ReadOnly); 260 stdplus::fd::LineReader reader(fd); 261 while (true) 262 { 263 parse.pump(*reader.readLine()); 264 } 265 } 266 catch (const stdplus::exception::Eof&) 267 { 268 } 269 catch (const std::system_error& e) 270 { 271 fileExists = false; 272 // TODO: Pass exceptions once callers can handle them 273 parse.warnings.emplace_back( 274 fmt::format("{}: Open error: {}", filename.native(), e.what())); 275 } 276 277 this->map = std::move(parse.map); 278 this->fileExists = fileExists; 279 this->filename = filename; 280 this->warnings = std::move(parse.warnings); 281 } 282 283 static void writeFileInt(const SectionMap& map, const fs::path& filename) 284 { 285 stdplus::fd::AtomicWriter writer(filename, 0644); 286 stdplus::fd::FormatBuffer out(writer); 287 for (const auto& [section, maps] : map) 288 { 289 for (const auto& map : maps) 290 { 291 out.append(FMT_COMPILE("[{}]\n"), section.get()); 292 for (const auto& [key, vals] : map) 293 { 294 for (const auto& val : vals) 295 { 296 out.append(FMT_COMPILE("{}={}\n"), key.get(), val.get()); 297 } 298 } 299 } 300 } 301 out.flush(); 302 writer.commit(); 303 } 304 305 void Parser::writeFile() const 306 { 307 writeFileInt(map, filename); 308 } 309 310 void Parser::writeFile(const fs::path& filename) 311 { 312 writeFileInt(map, filename); 313 this->filename = filename; 314 } 315 316 } // namespace config 317 } // namespace network 318 } // namespace phosphor 319