xref: /openbmc/entity-manager/src/utils.cpp (revision 3c61d7f4)
1 /*
2 // Copyright (c) 2017 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 /// \file utils.cpp
17 
18 #include "utils.hpp"
19 
20 #include "expression.hpp"
21 #include "variant_visitors.hpp"
22 
23 #include <boost/algorithm/string/classification.hpp>
24 #include <boost/algorithm/string/find.hpp>
25 #include <boost/algorithm/string/predicate.hpp>
26 #include <boost/algorithm/string/replace.hpp>
27 #include <boost/algorithm/string/split.hpp>
28 #include <boost/container/flat_map.hpp>
29 #include <boost/lexical_cast.hpp>
30 #include <sdbusplus/bus/match.hpp>
31 #include <valijson/adapters/nlohmann_json_adapter.hpp>
32 #include <valijson/schema.hpp>
33 #include <valijson/schema_parser.hpp>
34 #include <valijson/validator.hpp>
35 
36 #include <charconv>
37 #include <filesystem>
38 #include <fstream>
39 #include <map>
40 #include <regex>
41 
42 constexpr const char* templateChar = "$";
43 
44 namespace fs = std::filesystem;
45 static bool powerStatusOn = false;
46 static std::unique_ptr<sdbusplus::bus::match::match> powerMatch = nullptr;
47 
48 bool findFiles(const fs::path& dirPath, const std::string& matchString,
49                std::vector<fs::path>& foundPaths)
50 {
51     if (!fs::exists(dirPath))
52     {
53         return false;
54     }
55 
56     std::regex search(matchString);
57     std::smatch match;
58     for (const auto& p : fs::directory_iterator(dirPath))
59     {
60         std::string path = p.path().string();
61         if (std::regex_search(path, match, search))
62         {
63             foundPaths.emplace_back(p.path());
64         }
65     }
66     return true;
67 }
68 
69 bool findFiles(const std::vector<fs::path>&& dirPaths,
70                const std::string& matchString,
71                std::vector<fs::path>& foundPaths)
72 {
73     std::map<fs::path, fs::path> paths;
74     std::regex search(matchString);
75     std::smatch match;
76     for (const auto& dirPath : dirPaths)
77     {
78         if (!fs::exists(dirPath))
79         {
80             continue;
81         }
82 
83         for (const auto& p : fs::directory_iterator(dirPath))
84         {
85             std::string path = p.path().string();
86             if (std::regex_search(path, match, search))
87             {
88                 paths[p.path().filename()] = p.path();
89             }
90         }
91     }
92 
93     for (const auto& [key, value] : paths)
94     {
95         foundPaths.emplace_back(value);
96     }
97 
98     return !foundPaths.empty();
99 }
100 
101 bool getI2cDevicePaths(const fs::path& dirPath,
102                        boost::container::flat_map<size_t, fs::path>& busPaths)
103 {
104     if (!fs::exists(dirPath))
105     {
106         return false;
107     }
108 
109     // Regex for matching the path
110     std::regex searchPath(std::string(R"(i2c-\d+$)"));
111     // Regex for matching the bus numbers
112     std::regex searchBus(std::string(R"(\w[^-]*$)"));
113     std::smatch matchPath;
114     std::smatch matchBus;
115     for (const auto& p : fs::directory_iterator(dirPath))
116     {
117         std::string path = p.path().string();
118         if (std::regex_search(path, matchPath, searchPath))
119         {
120             if (std::regex_search(path, matchBus, searchBus))
121             {
122                 size_t bus = stoul(*matchBus.begin());
123                 busPaths.insert(std::pair<size_t, fs::path>(bus, p.path()));
124             }
125         }
126     }
127 
128     return true;
129 }
130 
131 bool validateJson(const nlohmann::json& schemaFile, const nlohmann::json& input)
132 {
133     valijson::Schema schema;
134     valijson::SchemaParser parser;
135     valijson::adapters::NlohmannJsonAdapter schemaAdapter(schemaFile);
136     parser.populateSchema(schemaAdapter, schema);
137     valijson::Validator validator;
138     valijson::adapters::NlohmannJsonAdapter targetAdapter(input);
139     if (!validator.validate(schema, targetAdapter, nullptr))
140     {
141         return false;
142     }
143     return true;
144 }
145 
146 bool isPowerOn(void)
147 {
148     if (!powerMatch)
149     {
150         throw std::runtime_error("Power Match Not Created");
151     }
152     return powerStatusOn;
153 }
154 
155 void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn)
156 {
157     powerMatch = std::make_unique<sdbusplus::bus::match::match>(
158         static_cast<sdbusplus::bus::bus&>(*conn),
159         "type='signal',interface='" + std::string(properties::interface) +
160             "',path='" + std::string(power::path) + "',arg0='" +
161             std::string(power::interface) + "'",
162         [](sdbusplus::message::message& message) {
163             std::string objectName;
164             boost::container::flat_map<std::string, std::variant<std::string>>
165                 values;
166             message.read(objectName, values);
167             auto findState = values.find(power::property);
168             if (findState != values.end())
169             {
170                 powerStatusOn = boost::ends_with(
171                     std::get<std::string>(findState->second), "Running");
172             }
173         });
174 
175     conn->async_method_call(
176         [](boost::system::error_code ec,
177            const std::variant<std::string>& state) {
178             if (ec)
179             {
180                 return;
181             }
182             powerStatusOn =
183                 boost::ends_with(std::get<std::string>(state), "Running");
184         },
185         power::busname, power::path, properties::interface, properties::get,
186         power::interface, power::property);
187 }
188 
189 // Replaces the template character like the other version of this function,
190 // but checks all properties on all interfaces provided to do the substitution
191 // with.
192 std::optional<std::string>
193     templateCharReplace(nlohmann::json::iterator& keyPair,
194                         const DBusObject& object, const size_t index,
195                         const std::optional<std::string>& replaceStr)
196 {
197     for (const auto& [_, interface] : object)
198     {
199         auto ret = templateCharReplace(keyPair, interface, index, replaceStr);
200         if (ret)
201         {
202             return ret;
203         }
204     }
205     return std::nullopt;
206 }
207 
208 // finds the template character (currently set to $) and replaces the value with
209 // the field found in a dbus object i.e. $ADDRESS would get populated with the
210 // ADDRESS field from a object on dbus
211 std::optional<std::string>
212     templateCharReplace(nlohmann::json::iterator& keyPair,
213                         const DBusInterface& interface, const size_t index,
214                         const std::optional<std::string>& replaceStr)
215 {
216     std::optional<std::string> ret = std::nullopt;
217 
218     if (keyPair.value().type() == nlohmann::json::value_t::object ||
219         keyPair.value().type() == nlohmann::json::value_t::array)
220     {
221         for (auto nextLayer = keyPair.value().begin();
222              nextLayer != keyPair.value().end(); nextLayer++)
223         {
224             templateCharReplace(nextLayer, interface, index, replaceStr);
225         }
226         return ret;
227     }
228 
229     std::string* strPtr = keyPair.value().get_ptr<std::string*>();
230     if (strPtr == nullptr)
231     {
232         return ret;
233     }
234 
235     boost::replace_all(*strPtr, std::string(templateChar) + "index",
236                        std::to_string(index));
237     if (replaceStr)
238     {
239         boost::replace_all(*strPtr, *replaceStr, std::to_string(index));
240     }
241 
242     for (auto& [propName, propValue] : interface)
243     {
244         std::string templateName = templateChar + propName;
245         boost::iterator_range<std::string::const_iterator> find =
246             boost::ifind_first(*strPtr, templateName);
247         if (!find)
248         {
249             continue;
250         }
251 
252         size_t start = find.begin() - strPtr->begin();
253 
254         // check for additional operations
255         if (!start && find.end() == strPtr->end())
256         {
257             std::visit([&](auto&& val) { keyPair.value() = val; }, propValue);
258             return ret;
259         }
260 
261         constexpr const std::array<char, 5> mathChars = {'+', '-', '%', '*',
262                                                          '/'};
263         size_t nextItemIdx = start + templateName.size() + 1;
264 
265         if (nextItemIdx > strPtr->size() ||
266             std::find(mathChars.begin(), mathChars.end(),
267                       strPtr->at(nextItemIdx)) == mathChars.end())
268         {
269             std::string val = std::visit(VariantToStringVisitor(), propValue);
270             boost::ireplace_all(*strPtr, templateName, val);
271             continue;
272         }
273 
274         // save the prefix
275         std::string prefix = strPtr->substr(0, start);
276 
277         // operate on the rest
278         std::string end = strPtr->substr(nextItemIdx);
279 
280         std::vector<std::string> split;
281         boost::split(split, end, boost::is_any_of(" "));
282 
283         // need at least 1 operation and number
284         if (split.size() < 2)
285         {
286             std::cerr << "Syntax error on template replacement of " << *strPtr
287                       << "\n";
288             for (const std::string& data : split)
289             {
290                 std::cerr << data << " ";
291             }
292             std::cerr << "\n";
293             continue;
294         }
295 
296         // we assume that the replacement is a number, because we can
297         // only do math on numbers.. we might concatenate strings in the
298         // future, but thats later
299         int number = std::visit(VariantToIntVisitor(), propValue);
300         auto exprBegin = split.begin();
301         auto exprEnd = split.end();
302 
303         number = expression::evaluate(number, exprBegin, exprEnd);
304 
305         std::string replaced(find.begin(), find.end());
306         while (exprBegin != exprEnd)
307         {
308             replaced.append(" ").append(*exprBegin++);
309         }
310         ret = replaced;
311 
312         std::string result = prefix + std::to_string(number);
313         while (exprEnd != split.end())
314         {
315             result.append(" ").append(*exprEnd++);
316         }
317         keyPair.value() = result;
318 
319         // We probably just invalidated the pointer abovei,
320         // reset and continue to handle multiple templates
321         strPtr = keyPair.value().get_ptr<std::string*>();
322         if (strPtr == nullptr)
323         {
324             break;
325         }
326     }
327 
328     strPtr = keyPair.value().get_ptr<std::string*>();
329     if (strPtr == nullptr)
330     {
331         return ret;
332     }
333 
334     std::string_view strView = *strPtr;
335     int base = 10;
336     if (boost::starts_with(strView, "0x"))
337     {
338         strView.remove_prefix(2);
339         base = 16;
340     }
341 
342     uint64_t temp = 0;
343     const char* strDataEndPtr = strView.data() + strView.size();
344     const std::from_chars_result res =
345         std::from_chars(strView.data(), strDataEndPtr, temp, base);
346     if (res.ec == std::errc{} && res.ptr == strDataEndPtr)
347     {
348         keyPair.value() = temp;
349     }
350 
351     return ret;
352 }
353 
354 /// \brief JSON/DBus matching Callable for std::variant (visitor)
355 ///
356 /// Default match JSON/DBus match implementation
357 /// \tparam T The concrete DBus value type from DBusValueVariant
358 template <typename T>
359 struct MatchProbe
360 {
361     /// \param probe the probe statement to match against
362     /// \param value the property value being matched to a probe
363     /// \return true if the dbusValue matched the probe otherwise false
364     static bool match(const nlohmann::json& probe, const T& value)
365     {
366         return probe == value;
367     }
368 };
369 
370 /// \brief JSON/DBus matching Callable for std::variant (visitor)
371 ///
372 /// std::string specialization of MatchProbe enabling regex matching
373 template <>
374 struct MatchProbe<std::string>
375 {
376     /// \param probe the probe statement to match against
377     /// \param value the string value being matched to a probe
378     /// \return true if the dbusValue matched the probe otherwise false
379     static bool match(const nlohmann::json& probe, const std::string& value)
380     {
381         if (probe.is_string())
382         {
383             try
384             {
385                 std::regex search(probe);
386                 std::smatch regMatch;
387                 return std::regex_search(value, regMatch, search);
388             }
389             catch (const std::regex_error&)
390             {
391                 std::cerr << "Syntax error in regular expression: " << probe
392                           << " will never match";
393             }
394         }
395 
396         // Skip calling nlohmann here, since it will never match a non-string
397         // to a std::string
398         return false;
399     }
400 };
401 
402 /// \brief Forwarding JSON/DBus matching Callable for std::variant (visitor)
403 ///
404 /// Forward calls to the correct template instantiation of MatchProbe
405 struct MatchProbeForwarder
406 {
407     explicit MatchProbeForwarder(const nlohmann::json& probe) : probeRef(probe)
408     {}
409     const nlohmann::json& probeRef;
410 
411     template <typename T>
412     bool operator()(const T& dbusValue) const
413     {
414         return MatchProbe<T>::match(probeRef, dbusValue);
415     }
416 };
417 
418 bool matchProbe(const nlohmann::json& probe, const DBusValueVariant& dbusValue)
419 {
420     return std::visit(MatchProbeForwarder(probe), dbusValue);
421 }
422