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