1 #include "config_parser.hpp" 2 3 #include <fmt/compile.h> 4 #include <fmt/format.h> 5 6 #include <stdplus/exception.hpp> 7 #include <stdplus/fd/atomic.hpp> 8 #include <stdplus/fd/create.hpp> 9 #include <stdplus/fd/fmt.hpp> 10 #include <stdplus/fd/line.hpp> 11 12 #include <functional> 13 #include <iterator> 14 #include <stdexcept> 15 #include <string> 16 #include <utility> 17 18 namespace phosphor 19 { 20 namespace network 21 { 22 namespace config 23 { 24 25 using std::literals::string_view_literals::operator""sv; 26 27 bool icaseeq(std::string_view in, std::string_view expected) noexcept 28 { 29 return std::equal(in.begin(), in.end(), expected.begin(), expected.end(), 30 [](auto a, auto b) { return tolower(a) == b; }); 31 } 32 33 std::optional<bool> parseBool(std::string_view in) noexcept 34 { 35 if (in == "1"sv || icaseeq(in, "yes"sv) || icaseeq(in, "y"sv) || 36 icaseeq(in, "true"sv) || icaseeq(in, "t"sv) || icaseeq(in, "on"sv)) 37 { 38 return true; 39 } 40 if (in == "0"sv || icaseeq(in, "no"sv) || icaseeq(in, "n"sv) || 41 icaseeq(in, "false"sv) || icaseeq(in, "f"sv) || icaseeq(in, "off"sv)) 42 { 43 return false; 44 } 45 return std::nullopt; 46 } 47 48 fs::path pathForIntfConf(const fs::path& dir, std::string_view intf) 49 { 50 return dir / fmt::format(FMT_COMPILE("00-bmc-{}.network"), intf); 51 } 52 53 fs::path pathForIntfDev(const fs::path& dir, std::string_view intf) 54 { 55 return dir / fmt::format(FMT_COMPILE("{}.netdev"), intf); 56 } 57 58 const std::string* 59 SectionMap::getLastValueString(std::string_view section, 60 std::string_view key) const noexcept 61 { 62 auto sit = find(section); 63 if (sit == end()) 64 { 65 return nullptr; 66 } 67 for (auto it = sit->second.rbegin(); it != sit->second.rend(); ++it) 68 { 69 auto kit = it->find(key); 70 if (kit == it->end() || kit->second.empty()) 71 { 72 continue; 73 } 74 return &kit->second.back().get(); 75 } 76 return nullptr; 77 } 78 79 std::vector<std::string> SectionMap::getValueStrings(std::string_view section, 80 std::string_view key) const 81 { 82 return getValues(section, key, 83 [](const Value& v) { return std::string(v); }); 84 } 85 86 void KeyCheck::operator()(std::string_view s) 87 { 88 for (auto c : s) 89 { 90 if (c == '\n' || c == '=') 91 { 92 throw std::invalid_argument( 93 fmt::format(FMT_COMPILE("Invalid Config Key: {}"), s)); 94 } 95 } 96 } 97 98 void SectionCheck::operator()(std::string_view s) 99 { 100 for (auto c : s) 101 { 102 if (c == '\n' || c == ']') 103 { 104 throw std::invalid_argument( 105 fmt::format(FMT_COMPILE("Invalid Config Section: {}"), s)); 106 } 107 } 108 } 109 110 void ValueCheck::operator()(std::string_view s) 111 { 112 for (auto c : s) 113 { 114 if (c == '\n') 115 { 116 throw std::invalid_argument( 117 fmt::format(FMT_COMPILE("Invalid Config Value: {}"), s)); 118 } 119 } 120 } 121 122 Parser::Parser(const fs::path& filename) 123 { 124 setFile(filename); 125 } 126 127 constexpr bool isspace(char c) noexcept 128 { 129 return c == ' ' || c == '\t'; 130 } 131 132 constexpr bool iscomment(char c) noexcept 133 { 134 return c == '#' || c == ';'; 135 } 136 137 static void removePadding(std::string_view& str) noexcept 138 { 139 size_t idx = str.size(); 140 for (; idx > 0 && isspace(str[idx - 1]); idx--) 141 ; 142 str.remove_suffix(str.size() - idx); 143 144 idx = 0; 145 for (; idx < str.size() && isspace(str[idx]); idx++) 146 ; 147 str.remove_prefix(idx); 148 } 149 150 struct Parse 151 { 152 std::reference_wrapper<const fs::path> filename; 153 SectionMap map; 154 KeyValuesMap* section; 155 std::vector<std::string> warnings; 156 size_t lineno; 157 158 inline Parse(const fs::path& filename) : 159 filename(filename), section(nullptr), lineno(0) 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 catch (const std::system_error& e) 269 { 270 fileExists = false; 271 // TODO: Pass exceptions once callers can handle them 272 parse.warnings.emplace_back( 273 fmt::format("{}: Open error: {}", filename.native(), e.what())); 274 } 275 276 this->map = std::move(parse.map); 277 this->fileExists = fileExists; 278 this->filename = filename; 279 this->warnings = std::move(parse.warnings); 280 } 281 282 static void writeFileInt(const SectionMap& map, const fs::path& filename) 283 { 284 stdplus::fd::AtomicWriter writer(filename, 0644); 285 stdplus::fd::FormatBuffer out(writer); 286 for (const auto& [section, maps] : map) 287 { 288 for (const auto& map : maps) 289 { 290 out.append(FMT_COMPILE("[{}]\n"), section.get()); 291 for (const auto& [key, vals] : map) 292 { 293 for (const auto& val : vals) 294 { 295 out.append(FMT_COMPILE("{}={}\n"), key.get(), val.get()); 296 } 297 } 298 } 299 } 300 out.flush(); 301 writer.commit(); 302 } 303 304 void Parser::writeFile() const 305 { 306 writeFileInt(map, filename); 307 } 308 309 void Parser::writeFile(const fs::path& filename) 310 { 311 writeFileInt(map, filename); 312 this->filename = filename; 313 } 314 315 } // namespace config 316 } // namespace network 317 } // namespace phosphor 318