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 
icaseeq(std::string_view in,std::string_view expected)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 
parseBool(std::string_view in)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 
pathForIntfConf(const fs::path & dir,std::string_view intf)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 
pathForIntfDev(const fs::path & dir,std::string_view intf)52 fs::path pathForIntfDev(const fs::path& dir, std::string_view intf)
53 {
54     return dir / stdplus::strCat(intf, ".netdev"sv);
55 }
56 
getLastValueString(std::string_view section,std::string_view key) const57 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 
getValueStrings(std::string_view section,std::string_view key) const77 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 
operator ()(std::string_view s)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 
operator ()(std::string_view s)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 
operator ()(std::string_view s)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 
Parser(const fs::path & filename)120 Parser::Parser(const fs::path& filename)
121 {
122     setFile(filename);
123 }
124 
isspace(char c)125 constexpr bool isspace(char c) noexcept
126 {
127     return c == ' ' || c == '\t';
128 }
129 
iscomment(char c)130 constexpr bool iscomment(char c) noexcept
131 {
132     return c == '#' || c == ';';
133 }
134 
removePadding(std::string_view & str)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 
Parsephosphor::network::config::Parse156     inline Parse(const fs::path& filename) :
157         filename(filename), section(nullptr), lineno(0)
158     {}
159 
pumpSectionphosphor::network::config::Parse160     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 
pumpKVphosphor::network::config::Parse191     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 
pumpphosphor::network::config::Parse227     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 
setFile(const fs::path & filename)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 
writeFileInt(const SectionMap & map,const fs::path & filename)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 
writeFile() const302 void Parser::writeFile() const
303 {
304     writeFileInt(map, filename);
305 }
306 
writeFile(const fs::path & filename)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