blob: dd13113eae2f532e846883e6c21fba2f696286ba [file] [log] [blame]
James Feist3cb5fec2018-01-23 14:41:51 -08001/*
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*/
Brad Bishop1fb9f3f2020-08-28 08:15:13 -040016/// \file Utils.cpp
James Feist3cb5fec2018-01-23 14:41:51 -080017
James Feist481c5d52019-08-13 14:40:40 -070018#include "Utils.hpp"
19
20#include "VariantVisitors.hpp"
21
22#include <boost/algorithm/string/classification.hpp>
23#include <boost/algorithm/string/find.hpp>
24#include <boost/algorithm/string/predicate.hpp>
25#include <boost/algorithm/string/replace.hpp>
26#include <boost/algorithm/string/split.hpp>
27#include <boost/container/flat_map.hpp>
28#include <boost/lexical_cast.hpp>
James Feist1df06a42019-04-11 14:23:04 -070029#include <sdbusplus/bus/match.hpp>
James Feistb4383f42018-08-06 16:54:10 -070030#include <valijson/adapters/nlohmann_json_adapter.hpp>
31#include <valijson/schema.hpp>
32#include <valijson/schema_parser.hpp>
33#include <valijson/validator.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080034
James Feist8c505da2020-05-28 10:06:33 -070035#include <filesystem>
36#include <fstream>
Andrew Jefferya9c58922021-06-01 09:28:59 +093037#include <map>
James Feist8c505da2020-05-28 10:06:33 -070038#include <regex>
39
James Feist481c5d52019-08-13 14:40:40 -070040constexpr const char* templateChar = "$";
41
Ed Tanous072e25d2018-12-16 21:45:20 -080042namespace fs = std::filesystem;
James Feist1df06a42019-04-11 14:23:04 -070043static bool powerStatusOn = false;
44static std::unique_ptr<sdbusplus::bus::match::match> powerMatch = nullptr;
James Feist3cb5fec2018-01-23 14:41:51 -080045
James Feista465ccc2019-02-08 12:51:01 -080046bool findFiles(const fs::path& dirPath, const std::string& matchString,
47 std::vector<fs::path>& foundPaths)
James Feist3cb5fec2018-01-23 14:41:51 -080048{
James Feista3c180a2018-08-09 16:06:04 -070049 if (!fs::exists(dirPath))
Ed Tanous07d467b2021-02-23 14:48:37 -080050 {
James Feist3cb5fec2018-01-23 14:41:51 -080051 return false;
Ed Tanous07d467b2021-02-23 14:48:37 -080052 }
James Feist3cb5fec2018-01-23 14:41:51 -080053
James Feista3c180a2018-08-09 16:06:04 -070054 std::regex search(matchString);
James Feist3cb5fec2018-01-23 14:41:51 -080055 std::smatch match;
James Feista465ccc2019-02-08 12:51:01 -080056 for (const auto& p : fs::directory_iterator(dirPath))
James Feist3cb5fec2018-01-23 14:41:51 -080057 {
58 std::string path = p.path().string();
James Feistc95cb142018-02-26 10:41:42 -080059 if (std::regex_search(path, match, search))
James Feist3cb5fec2018-01-23 14:41:51 -080060 {
James Feista3c180a2018-08-09 16:06:04 -070061 foundPaths.emplace_back(p.path());
James Feist3cb5fec2018-01-23 14:41:51 -080062 }
63 }
64 return true;
James Feistb4383f42018-08-06 16:54:10 -070065}
66
Andrew Jefferya9c58922021-06-01 09:28:59 +093067bool 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))
Bruce Mitchella6d47332021-12-13 10:18:03 -060077 {
Andrew Jefferya9c58922021-06-01 09:28:59 +093078 continue;
Bruce Mitchella6d47332021-12-13 10:18:03 -060079 }
Andrew Jefferya9c58922021-06-01 09:28:59 +093080
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
Nikhil Potaded8884f12019-03-27 13:27:13 -070099bool 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
James Feista465ccc2019-02-08 12:51:01 -0800129bool validateJson(const nlohmann::json& schemaFile, const nlohmann::json& input)
James Feistb4383f42018-08-06 16:54:10 -0700130{
131 valijson::Schema schema;
132 valijson::SchemaParser parser;
133 valijson::adapters::NlohmannJsonAdapter schemaAdapter(schemaFile);
134 parser.populateSchema(schemaAdapter, schema);
135 valijson::Validator validator;
136 valijson::adapters::NlohmannJsonAdapter targetAdapter(input);
Ed Tanous07d467b2021-02-23 14:48:37 -0800137 if (!validator.validate(schema, targetAdapter, nullptr))
James Feistb4383f42018-08-06 16:54:10 -0700138 {
139 return false;
140 }
141 return true;
Ed Tanous072e25d2018-12-16 21:45:20 -0800142}
James Feist1df06a42019-04-11 14:23:04 -0700143
144bool isPowerOn(void)
145{
146 if (!powerMatch)
147 {
148 throw std::runtime_error("Power Match Not Created");
149 }
150 return powerStatusOn;
151}
152
153void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn)
154{
James Feist1df06a42019-04-11 14:23:04 -0700155 powerMatch = std::make_unique<sdbusplus::bus::match::match>(
156 static_cast<sdbusplus::bus::bus&>(*conn),
James Feist61f5ac42020-03-11 14:37:25 -0700157 "type='signal',interface='" + std::string(properties::interface) +
158 "',path='" + std::string(power::path) + "',arg0='" +
159 std::string(power::interface) + "'",
160 [](sdbusplus::message::message& 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);
James Feist481c5d52019-08-13 14:40:40 -0700185}
186
Matt Spinlere789bf12021-02-24 12:33:01 -0600187// 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.
Andrew Jeffery1983d2f2022-04-05 14:55:13 +0930190std::optional<std::string>
191 templateCharReplace(nlohmann::json::iterator& keyPair,
192 const DBusObject& object, const size_t index,
193 const std::optional<std::string>& replaceStr)
Matt Spinlere789bf12021-02-24 12:33:01 -0600194{
Andrew Jeffery1983d2f2022-04-05 14:55:13 +0930195 for (const auto& [_, interface] : object)
Matt Spinlere789bf12021-02-24 12:33:01 -0600196 {
Andrew Jeffery1983d2f2022-04-05 14:55:13 +0930197 auto ret = templateCharReplace(keyPair, interface, index, replaceStr);
Matt Spinlere789bf12021-02-24 12:33:01 -0600198 if (ret)
199 {
200 return ret;
201 }
202 }
203 return std::nullopt;
204}
205
James Feist481c5d52019-08-13 14:40:40 -0700206// 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
Andrew Jeffery1983d2f2022-04-05 14:55:13 +0930209std::optional<std::string>
210 templateCharReplace(nlohmann::json::iterator& keyPair,
211 const DBusInterface& interface, const size_t index,
212 const std::optional<std::string>& replaceStr)
James Feist481c5d52019-08-13 14:40:40 -0700213{
James Feist35f5e0e2020-03-16 14:02:27 -0700214 std::optional<std::string> ret = std::nullopt;
215
James Feist481c5d52019-08-13 14:40:40 -0700216 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 {
Andrew Jeffery1983d2f2022-04-05 14:55:13 +0930222 templateCharReplace(nextLayer, interface, index, replaceStr);
James Feist481c5d52019-08-13 14:40:40 -0700223 }
James Feist35f5e0e2020-03-16 14:02:27 -0700224 return ret;
James Feist481c5d52019-08-13 14:40:40 -0700225 }
226
227 std::string* strPtr = keyPair.value().get_ptr<std::string*>();
228 if (strPtr == nullptr)
229 {
James Feist35f5e0e2020-03-16 14:02:27 -0700230 return ret;
James Feist481c5d52019-08-13 14:40:40 -0700231 }
232
233 boost::replace_all(*strPtr, std::string(templateChar) + "index",
Andrew Jeffery1983d2f2022-04-05 14:55:13 +0930234 std::to_string(index));
James Feist35f5e0e2020-03-16 14:02:27 -0700235 if (replaceStr)
236 {
Andrew Jeffery1983d2f2022-04-05 14:55:13 +0930237 boost::replace_all(*strPtr, *replaceStr, std::to_string(index));
James Feist35f5e0e2020-03-16 14:02:27 -0700238 }
James Feist481c5d52019-08-13 14:40:40 -0700239
Andrew Jefferyd9e3e192022-04-07 12:30:45 +0930240 for (auto& [propName, propValue] : interface)
James Feist481c5d52019-08-13 14:40:40 -0700241 {
Andrew Jefferyd9e3e192022-04-07 12:30:45 +0930242 std::string templateName = templateChar + propName;
James Feist481c5d52019-08-13 14:40:40 -0700243 boost::iterator_range<std::string::const_iterator> find =
244 boost::ifind_first(*strPtr, templateName);
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930245 if (!find)
James Feist481c5d52019-08-13 14:40:40 -0700246 {
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930247 continue;
248 }
James Feist8c20feb2019-08-14 15:10:11 -0700249
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930250 size_t start = find.begin() - strPtr->begin();
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930251
252 // check for additional operations
253 if (!start && find.end() == strPtr->end())
254 {
Andrew Jefferyd9e3e192022-04-07 12:30:45 +0930255 std::visit([&](auto&& val) { keyPair.value() = val; }, propValue);
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930256 return ret;
257 }
Andrew Jeffery7d05afc2022-04-07 15:12:10 +0930258
259 constexpr const std::array<char, 5> mathChars = {'+', '-', '%', '*',
260 '/'};
261 size_t nextItemIdx = start + templateName.size() + 1;
262
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930263 if (nextItemIdx > strPtr->size() ||
264 std::find(mathChars.begin(), mathChars.end(),
265 strPtr->at(nextItemIdx)) == mathChars.end())
266 {
Andrew Jefferyd9e3e192022-04-07 12:30:45 +0930267 std::string val = std::visit(VariantToStringVisitor(), propValue);
Andrew Jeffery8d8b62f2022-04-07 12:32:13 +0930268 boost::ireplace_all(*strPtr, templateName, val);
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930269 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)
James Feist481c5d52019-08-13 14:40:40 -0700287 {
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930288 std::cerr << data << " ";
James Feist481c5d52019-08-13 14:40:40 -0700289 }
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930290 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
Andrew Jefferyd9e3e192022-04-07 12:30:45 +0930297 int number = std::visit(VariantToIntVisitor(), propValue);
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930298
299 bool isOperator = true;
300 TemplateOperation next = TemplateOperation::addition;
301
302 auto it = split.begin();
303
304 for (; it != split.end(); it++)
305 {
306 if (isOperator)
James Feist481c5d52019-08-13 14:40:40 -0700307 {
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930308 if (*it == "+")
James Feist481c5d52019-08-13 14:40:40 -0700309 {
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930310 next = TemplateOperation::addition;
James Feist481c5d52019-08-13 14:40:40 -0700311 }
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930312 else if (*it == "-")
James Feist481c5d52019-08-13 14:40:40 -0700313 {
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930314 next = TemplateOperation::subtraction;
315 }
316 else if (*it == "*")
317 {
318 next = TemplateOperation::multiplication;
319 }
320 else if (*it == R"(%)")
321 {
322 next = TemplateOperation::modulo;
323 }
324 else if (*it == R"(/)")
325 {
326 next = TemplateOperation::division;
James Feist481c5d52019-08-13 14:40:40 -0700327 }
328 else
329 {
James Feist35f5e0e2020-03-16 14:02:27 -0700330 break;
331 }
332 }
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930333 else
James Feist481c5d52019-08-13 14:40:40 -0700334 {
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930335 int constant = 0;
336 try
James Feist481c5d52019-08-13 14:40:40 -0700337 {
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930338 constant = std::stoi(*it);
339 }
340 catch (const std::invalid_argument&)
341 {
342 std::cerr << "Parameter not supported for templates " << *it
343 << "\n";
344 continue;
345 }
346 switch (next)
347 {
348 case TemplateOperation::addition:
349 {
350 number += constant;
351 break;
352 }
353 case TemplateOperation::subtraction:
354 {
355 number -= constant;
356 break;
357 }
358 case TemplateOperation::multiplication:
359 {
360 number *= constant;
361 break;
362 }
363 case TemplateOperation::division:
364 {
365 number /= constant;
366 break;
367 }
368 case TemplateOperation::modulo:
369 {
370 number = number % constant;
371 break;
372 }
373
374 default:
375 break;
James Feist481c5d52019-08-13 14:40:40 -0700376 }
377 }
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930378 isOperator = !isOperator;
379 }
James Feist481c5d52019-08-13 14:40:40 -0700380
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930381 std::string result = prefix + std::to_string(number);
382
383 std::string replaced(find.begin(), find.end());
384 for (auto it2 = split.begin(); it2 != split.end(); it2++)
385 {
386 replaced += " ";
387 replaced += *it2;
388 if (it2 == it)
Zhikui Rena0d1b3f2021-10-05 16:21:56 -0700389 {
390 break;
391 }
James Feist481c5d52019-08-13 14:40:40 -0700392 }
Andrew Jefferye0ed5872022-04-07 12:16:33 +0930393 ret = replaced;
394
395 if (it != split.end())
396 {
397 for (; it != split.end(); it++)
398 {
399 result += " " + *it;
400 }
401 }
402 keyPair.value() = result;
403
404 // We probably just invalidated the pointer abovei,
405 // reset and continue to handle multiple templates
406 strPtr = keyPair.value().get_ptr<std::string*>();
407 if (strPtr == nullptr)
408 {
409 break;
410 }
James Feist481c5d52019-08-13 14:40:40 -0700411 }
412
413 strPtr = keyPair.value().get_ptr<std::string*>();
414 if (strPtr == nullptr)
415 {
James Feist35f5e0e2020-03-16 14:02:27 -0700416 return ret;
James Feist481c5d52019-08-13 14:40:40 -0700417 }
418
419 // convert hex numbers to ints
420 if (boost::starts_with(*strPtr, "0x"))
421 {
422 try
423 {
424 size_t pos = 0;
425 int64_t temp = std::stoul(*strPtr, &pos, 0);
426 if (pos == strPtr->size())
427 {
428 keyPair.value() = static_cast<uint64_t>(temp);
429 }
430 }
Patrick Williams83b1e9b2021-10-06 12:47:24 -0500431 catch (const std::invalid_argument&)
James Feist8c505da2020-05-28 10:06:33 -0700432 {}
Patrick Williams83b1e9b2021-10-06 12:47:24 -0500433 catch (const std::out_of_range&)
James Feist8c505da2020-05-28 10:06:33 -0700434 {}
James Feist481c5d52019-08-13 14:40:40 -0700435 }
436 // non-hex numbers
437 else
438 {
439 try
440 {
441 uint64_t temp = boost::lexical_cast<uint64_t>(*strPtr);
442 keyPair.value() = temp;
443 }
Patrick Williams83b1e9b2021-10-06 12:47:24 -0500444 catch (const boost::bad_lexical_cast&)
James Feist8c505da2020-05-28 10:06:33 -0700445 {}
James Feist481c5d52019-08-13 14:40:40 -0700446 }
James Feist35f5e0e2020-03-16 14:02:27 -0700447 return ret;
Xiang Liu2801a702020-01-20 14:29:34 -0800448}
Brad Bishop3cb8a602020-08-25 17:40:54 -0400449
Brad Bishop5d525412020-08-26 08:50:50 -0400450/// \brief JSON/DBus matching Callable for std::variant (visitor)
451///
452/// Default match JSON/DBus match implementation
Andrew Jefferyeab49292022-04-05 14:42:20 +0930453/// \tparam T The concrete DBus value type from DBusValueVariant
Brad Bishop5d525412020-08-26 08:50:50 -0400454template <typename T>
455struct MatchProbe
456{
457 /// \param probe the probe statement to match against
458 /// \param value the property value being matched to a probe
459 /// \return true if the dbusValue matched the probe otherwise false
460 static bool match(const nlohmann::json& probe, const T& value)
461 {
462 return probe == value;
463 }
464};
465
466/// \brief JSON/DBus matching Callable for std::variant (visitor)
467///
468/// std::string specialization of MatchProbe enabling regex matching
469template <>
470struct MatchProbe<std::string>
471{
472 /// \param probe the probe statement to match against
473 /// \param value the string value being matched to a probe
474 /// \return true if the dbusValue matched the probe otherwise false
475 static bool match(const nlohmann::json& probe, const std::string& value)
476 {
477 if (probe.is_string())
478 {
479 try
480 {
481 std::regex search(probe);
482 std::smatch regMatch;
483 return std::regex_search(value, regMatch, search);
484 }
485 catch (const std::regex_error&)
486 {
487 std::cerr << "Syntax error in regular expression: " << probe
488 << " will never match";
489 }
490 }
491
492 // Skip calling nlohmann here, since it will never match a non-string
493 // to a std::string
494 return false;
495 }
496};
497
498/// \brief Forwarding JSON/DBus matching Callable for std::variant (visitor)
499///
500/// Forward calls to the correct template instantiation of MatchProbe
501struct MatchProbeForwarder
502{
503 explicit MatchProbeForwarder(const nlohmann::json& probe) : probeRef(probe)
504 {}
505 const nlohmann::json& probeRef;
506
507 template <typename T>
508 bool operator()(const T& dbusValue) const
509 {
510 return MatchProbe<T>::match(probeRef, dbusValue);
511 }
512};
513
Andrew Jefferyeab49292022-04-05 14:42:20 +0930514bool matchProbe(const nlohmann::json& probe, const DBusValueVariant& dbusValue)
Brad Bishop3cb8a602020-08-25 17:40:54 -0400515{
Brad Bishop5d525412020-08-26 08:50:50 -0400516 return std::visit(MatchProbeForwarder(probe), dbusValue);
517}