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