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