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 
57 const std::string*
getLastValueString(std::string_view section,std::string_view key) const58     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 
getValueStrings(std::string_view section,std::string_view key) const78 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 
operator ()(std::string_view s)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 
operator ()(std::string_view s)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 
operator ()(std::string_view s)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 
Parser(const fs::path & filename)121 Parser::Parser(const fs::path& filename)
122 {
123     setFile(filename);
124 }
125 
isspace(char c)126 constexpr bool isspace(char c) noexcept
127 {
128     return c == ' ' || c == '\t';
129 }
130 
iscomment(char c)131 constexpr bool iscomment(char c) noexcept
132 {
133     return c == '#' || c == ';';
134 }
135 
removePadding(std::string_view & str)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 
Parsephosphor::network::config::Parse157     inline Parse(const fs::path& filename) :
158         filename(filename), section(nullptr), lineno(0)
159     {}
160 
pumpSectionphosphor::network::config::Parse161     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 
pumpKVphosphor::network::config::Parse192     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 
pumpphosphor::network::config::Parse228     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 
setFile(const fs::path & filename)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 
writeFileInt(const SectionMap & map,const fs::path & filename)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 
writeFile() const303 void Parser::writeFile() const
304 {
305     writeFileInt(map, filename);
306 }
307 
writeFile(const fs::path & filename)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