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 =
181 boost::ends_with(std::get<std::string>(state), "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.
templateCharReplace(nlohmann::json::iterator & keyPair,const DBusObject & object,const size_t index,const std::optional<std::string> & replaceStr)190 std::optional<std::string> templateCharReplace(
191 nlohmann::json::iterator& keyPair, const DBusObject& object,
192 const size_t index, const std::optional<std::string>& replaceStr)
193 {
194 for (const auto& [_, interface] : object)
195 {
196 auto ret = templateCharReplace(keyPair, interface, index, replaceStr);
197 if (ret)
198 {
199 return ret;
200 }
201 }
202 return std::nullopt;
203 }
204
205 // finds the template character (currently set to $) and replaces the value with
206 // the field found in a dbus object i.e. $ADDRESS would get populated with the
207 // ADDRESS field from a object on dbus
templateCharReplace(nlohmann::json::iterator & keyPair,const DBusInterface & interface,const size_t index,const std::optional<std::string> & replaceStr)208 std::optional<std::string> templateCharReplace(
209 nlohmann::json::iterator& keyPair, const DBusInterface& interface,
210 const size_t index, const std::optional<std::string>& replaceStr)
211 {
212 std::optional<std::string> ret = std::nullopt;
213
214 if (keyPair.value().type() == nlohmann::json::value_t::object ||
215 keyPair.value().type() == nlohmann::json::value_t::array)
216 {
217 for (auto nextLayer = keyPair.value().begin();
218 nextLayer != keyPair.value().end(); nextLayer++)
219 {
220 templateCharReplace(nextLayer, interface, index, replaceStr);
221 }
222 return ret;
223 }
224
225 std::string* strPtr = keyPair.value().get_ptr<std::string*>();
226 if (strPtr == nullptr)
227 {
228 return ret;
229 }
230
231 boost::replace_all(*strPtr, std::string(templateChar) + "index",
232 std::to_string(index));
233 if (replaceStr)
234 {
235 boost::replace_all(*strPtr, *replaceStr, std::to_string(index));
236 }
237
238 for (const auto& [propName, propValue] : interface)
239 {
240 std::string templateName = templateChar + propName;
241 boost::iterator_range<std::string::const_iterator> find =
242 boost::ifind_first(*strPtr, templateName);
243 if (!find)
244 {
245 continue;
246 }
247
248 size_t start = find.begin() - strPtr->begin();
249
250 // check for additional operations
251 if ((start == 0U) && find.end() == strPtr->end())
252 {
253 std::visit([&](auto&& val) { keyPair.value() = val; }, propValue);
254 return ret;
255 }
256
257 constexpr const std::array<char, 5> mathChars = {'+', '-', '%', '*',
258 '/'};
259 size_t nextItemIdx = start + templateName.size() + 1;
260
261 if (nextItemIdx > strPtr->size() ||
262 std::find(mathChars.begin(), mathChars.end(),
263 strPtr->at(nextItemIdx)) == mathChars.end())
264 {
265 std::string val = std::visit(VariantToStringVisitor(), propValue);
266 boost::ireplace_all(*strPtr, templateName, val);
267 continue;
268 }
269
270 // save the prefix
271 std::string prefix = strPtr->substr(0, start);
272
273 // operate on the rest
274 std::string end = strPtr->substr(nextItemIdx);
275
276 std::vector<std::string> split;
277 boost::split(split, end, boost::is_any_of(" "));
278
279 // need at least 1 operation and number
280 if (split.size() < 2)
281 {
282 std::cerr << "Syntax error on template replacement of " << *strPtr
283 << "\n";
284 for (const std::string& data : split)
285 {
286 std::cerr << data << " ";
287 }
288 std::cerr << "\n";
289 continue;
290 }
291
292 // we assume that the replacement is a number, because we can
293 // only do math on numbers.. we might concatenate strings in the
294 // future, but thats later
295 int number = std::visit(VariantToIntVisitor(), propValue);
296 auto exprBegin = split.begin();
297 auto exprEnd = split.end();
298
299 number = expression::evaluate(number, exprBegin, exprEnd);
300
301 std::string replaced(find.begin(), find.end());
302 while (exprBegin != exprEnd)
303 {
304 replaced.append(" ").append(*exprBegin++);
305 }
306 ret = replaced;
307
308 std::string result = prefix + std::to_string(number);
309 while (exprEnd != split.end())
310 {
311 result.append(" ").append(*exprEnd++);
312 }
313 keyPair.value() = result;
314
315 // We probably just invalidated the pointer abovei,
316 // reset and continue to handle multiple templates
317 strPtr = keyPair.value().get_ptr<std::string*>();
318 if (strPtr == nullptr)
319 {
320 break;
321 }
322 }
323
324 strPtr = keyPair.value().get_ptr<std::string*>();
325 if (strPtr == nullptr)
326 {
327 return ret;
328 }
329
330 std::string_view strView = *strPtr;
331 int base = 10;
332 if (boost::starts_with(strView, "0x"))
333 {
334 strView.remove_prefix(2);
335 base = 16;
336 }
337
338 uint64_t temp = 0;
339 const char* strDataEndPtr = strView.data() + strView.size();
340 const std::from_chars_result res =
341 std::from_chars(strView.data(), strDataEndPtr, temp, base);
342 if (res.ec == std::errc{} && res.ptr == strDataEndPtr)
343 {
344 keyPair.value() = temp;
345 }
346
347 return ret;
348 }
349
350 /// \brief JSON/DBus matching Callable for std::variant (visitor)
351 ///
352 /// Default match JSON/DBus match implementation
353 /// \tparam T The concrete DBus value type from DBusValueVariant
354 template <typename T>
355 struct MatchProbe
356 {
357 /// \param probe the probe statement to match against
358 /// \param value the property value being matched to a probe
359 /// \return true if the dbusValue matched the probe otherwise false
matchMatchProbe360 static bool match(const nlohmann::json& probe, const T& value)
361 {
362 return probe == value;
363 }
364 };
365
366 /// \brief JSON/DBus matching Callable for std::variant (visitor)
367 ///
368 /// std::string specialization of MatchProbe enabling regex matching
369 template <>
370 struct MatchProbe<std::string>
371 {
372 /// \param probe the probe statement to match against
373 /// \param value the string value being matched to a probe
374 /// \return true if the dbusValue matched the probe otherwise false
matchMatchProbe375 static bool match(const nlohmann::json& probe, const std::string& value)
376 {
377 if (probe.is_string())
378 {
379 try
380 {
381 std::regex search(probe);
382 std::smatch regMatch;
383 return std::regex_search(value, regMatch, search);
384 }
385 catch (const std::regex_error&)
386 {
387 std::cerr << "Syntax error in regular expression: " << probe
388 << " will never match";
389 }
390 }
391
392 // Skip calling nlohmann here, since it will never match a non-string
393 // to a std::string
394 return false;
395 }
396 };
397
398 /// \brief Forwarding JSON/DBus matching Callable for std::variant (visitor)
399 ///
400 /// Forward calls to the correct template instantiation of MatchProbe
401 struct MatchProbeForwarder
402 {
MatchProbeForwarderMatchProbeForwarder403 explicit MatchProbeForwarder(const nlohmann::json& probe) : probeRef(probe)
404 {}
405 const nlohmann::json& probeRef;
406
407 template <typename T>
operator ()MatchProbeForwarder408 bool operator()(const T& dbusValue) const
409 {
410 return MatchProbe<T>::match(probeRef, dbusValue);
411 }
412 };
413
matchProbe(const nlohmann::json & probe,const DBusValueVariant & dbusValue)414 bool matchProbe(const nlohmann::json& probe, const DBusValueVariant& dbusValue)
415 {
416 return std::visit(MatchProbeForwarder(probe), dbusValue);
417 }
418