blob: 489d47ae51493d38546a6f965a2a1ed8246fcb05 [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))
77 continue;
78
79 for (const auto& p : fs::directory_iterator(dirPath))
80 {
81 std::string path = p.path().string();
82 if (std::regex_search(path, match, search))
83 {
84 paths[p.path().filename()] = p.path();
85 }
86 }
87 }
88
89 for (const auto& [key, value] : paths)
90 {
91 foundPaths.emplace_back(value);
92 }
93
94 return !foundPaths.empty();
95}
96
Nikhil Potaded8884f12019-03-27 13:27:13 -070097bool getI2cDevicePaths(const fs::path& dirPath,
98 boost::container::flat_map<size_t, fs::path>& busPaths)
99{
100 if (!fs::exists(dirPath))
101 {
102 return false;
103 }
104
105 // Regex for matching the path
106 std::regex searchPath(std::string(R"(i2c-\d+$)"));
107 // Regex for matching the bus numbers
108 std::regex searchBus(std::string(R"(\w[^-]*$)"));
109 std::smatch matchPath;
110 std::smatch matchBus;
111 for (const auto& p : fs::directory_iterator(dirPath))
112 {
113 std::string path = p.path().string();
114 if (std::regex_search(path, matchPath, searchPath))
115 {
116 if (std::regex_search(path, matchBus, searchBus))
117 {
118 size_t bus = stoul(*matchBus.begin());
119 busPaths.insert(std::pair<size_t, fs::path>(bus, p.path()));
120 }
121 }
122 }
123
124 return true;
125}
126
James Feista465ccc2019-02-08 12:51:01 -0800127bool validateJson(const nlohmann::json& schemaFile, const nlohmann::json& input)
James Feistb4383f42018-08-06 16:54:10 -0700128{
129 valijson::Schema schema;
130 valijson::SchemaParser parser;
131 valijson::adapters::NlohmannJsonAdapter schemaAdapter(schemaFile);
132 parser.populateSchema(schemaAdapter, schema);
133 valijson::Validator validator;
134 valijson::adapters::NlohmannJsonAdapter targetAdapter(input);
Ed Tanous07d467b2021-02-23 14:48:37 -0800135 if (!validator.validate(schema, targetAdapter, nullptr))
James Feistb4383f42018-08-06 16:54:10 -0700136 {
137 return false;
138 }
139 return true;
Ed Tanous072e25d2018-12-16 21:45:20 -0800140}
James Feist1df06a42019-04-11 14:23:04 -0700141
142bool isPowerOn(void)
143{
144 if (!powerMatch)
145 {
146 throw std::runtime_error("Power Match Not Created");
147 }
148 return powerStatusOn;
149}
150
151void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn)
152{
James Feist1df06a42019-04-11 14:23:04 -0700153 powerMatch = std::make_unique<sdbusplus::bus::match::match>(
154 static_cast<sdbusplus::bus::bus&>(*conn),
James Feist61f5ac42020-03-11 14:37:25 -0700155 "type='signal',interface='" + std::string(properties::interface) +
156 "',path='" + std::string(power::path) + "',arg0='" +
157 std::string(power::interface) + "'",
158 [](sdbusplus::message::message& message) {
159 std::string objectName;
160 boost::container::flat_map<std::string, std::variant<std::string>>
161 values;
162 message.read(objectName, values);
163 auto findState = values.find(power::property);
164 if (findState != values.end())
165 {
166 powerStatusOn = boost::ends_with(
167 std::get<std::string>(findState->second), "Running");
168 }
169 });
170
171 conn->async_method_call(
172 [](boost::system::error_code ec,
173 const std::variant<std::string>& state) {
174 if (ec)
175 {
176 return;
177 }
178 powerStatusOn =
179 boost::ends_with(std::get<std::string>(state), "Running");
180 },
181 power::busname, power::path, properties::interface, properties::get,
182 power::interface, power::property);
James Feist481c5d52019-08-13 14:40:40 -0700183}
184
Matt Spinlere789bf12021-02-24 12:33:01 -0600185// Replaces the template character like the other version of this function,
186// but checks all properties on all interfaces provided to do the substitution
187// with.
188std::optional<std::string> templateCharReplace(
189 nlohmann::json::iterator& keyPair,
190 const boost::container::flat_map<
191 std::string, boost::container::flat_map<std::string, BasicVariantType>>&
192 allInterfaces,
193 const size_t foundDeviceIdx, const std::optional<std::string>& replaceStr)
194{
195 for (const auto& [interface, properties] : allInterfaces)
196 {
197 auto ret = templateCharReplace(keyPair, properties, foundDeviceIdx,
198 replaceStr);
199 if (ret)
200 {
201 return ret;
202 }
203 }
204 return std::nullopt;
205}
206
James Feist481c5d52019-08-13 14:40:40 -0700207// finds the template character (currently set to $) and replaces the value with
208// the field found in a dbus object i.e. $ADDRESS would get populated with the
209// ADDRESS field from a object on dbus
James Feist35f5e0e2020-03-16 14:02:27 -0700210std::optional<std::string> templateCharReplace(
James Feist481c5d52019-08-13 14:40:40 -0700211 nlohmann::json::iterator& keyPair,
212 const boost::container::flat_map<std::string, BasicVariantType>&
213 foundDevice,
James Feist35f5e0e2020-03-16 14:02:27 -0700214 const size_t foundDeviceIdx, const std::optional<std::string>& replaceStr)
James Feist481c5d52019-08-13 14:40:40 -0700215{
James Feist35f5e0e2020-03-16 14:02:27 -0700216 std::optional<std::string> ret = std::nullopt;
217
James Feist481c5d52019-08-13 14:40:40 -0700218 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 {
James Feist35f5e0e2020-03-16 14:02:27 -0700224 templateCharReplace(nextLayer, foundDevice, foundDeviceIdx,
225 replaceStr);
James Feist481c5d52019-08-13 14:40:40 -0700226 }
James Feist35f5e0e2020-03-16 14:02:27 -0700227 return ret;
James Feist481c5d52019-08-13 14:40:40 -0700228 }
229
230 std::string* strPtr = keyPair.value().get_ptr<std::string*>();
231 if (strPtr == nullptr)
232 {
James Feist35f5e0e2020-03-16 14:02:27 -0700233 return ret;
James Feist481c5d52019-08-13 14:40:40 -0700234 }
235
236 boost::replace_all(*strPtr, std::string(templateChar) + "index",
237 std::to_string(foundDeviceIdx));
James Feist35f5e0e2020-03-16 14:02:27 -0700238 if (replaceStr)
239 {
240 boost::replace_all(*strPtr, *replaceStr,
241 std::to_string(foundDeviceIdx));
242 }
James Feist481c5d52019-08-13 14:40:40 -0700243
244 for (auto& foundDevicePair : foundDevice)
245 {
246 std::string templateName = templateChar + foundDevicePair.first;
247 boost::iterator_range<std::string::const_iterator> find =
248 boost::ifind_first(*strPtr, templateName);
249 if (find)
250 {
James Feist8c20feb2019-08-14 15:10:11 -0700251 constexpr const std::array<char, 5> mathChars = {'+', '-', '%', '*',
252 '/'};
James Feist481c5d52019-08-13 14:40:40 -0700253 size_t start = find.begin() - strPtr->begin();
James Feist8c20feb2019-08-14 15:10:11 -0700254 size_t nextItemIdx = start + templateName.size() + 1;
255
James Feist481c5d52019-08-13 14:40:40 -0700256 // check for additional operations
257 if (!start && find.end() == strPtr->end())
258 {
259 std::visit([&](auto&& val) { keyPair.value() = val; },
260 foundDevicePair.second);
James Feist35f5e0e2020-03-16 14:02:27 -0700261 return ret;
James Feist481c5d52019-08-13 14:40:40 -0700262 }
Ed Tanous07d467b2021-02-23 14:48:37 -0800263 if (nextItemIdx > strPtr->size() ||
264 std::find(mathChars.begin(), mathChars.end(),
265 strPtr->at(nextItemIdx)) == mathChars.end())
James Feist481c5d52019-08-13 14:40:40 -0700266 {
267 std::string val = std::visit(VariantToStringVisitor(),
268 foundDevicePair.second);
James Feistb0097d42019-08-15 09:24:13 -0700269 boost::ireplace_all(*strPtr,
270 templateChar + foundDevicePair.first, val);
James Feist8c20feb2019-08-14 15:10:11 -0700271 continue;
James Feist481c5d52019-08-13 14:40:40 -0700272 }
273
274 // save the prefix
275 std::string prefix = strPtr->substr(0, start);
276
James Feist8c20feb2019-08-14 15:10:11 -0700277 // operate on the rest
278 std::string end = strPtr->substr(nextItemIdx);
James Feist481c5d52019-08-13 14:40:40 -0700279
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 "
287 << *strPtr << "\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 =
300 std::visit(VariantToIntVisitor(), foundDevicePair.second);
301
302 bool isOperator = true;
303 TemplateOperation next = TemplateOperation::addition;
304
305 auto it = split.begin();
306
307 for (; it != split.end(); it++)
308 {
309 if (isOperator)
310 {
311 if (*it == "+")
312 {
313 next = TemplateOperation::addition;
314 }
315 else if (*it == "-")
316 {
317 next = TemplateOperation::subtraction;
318 }
319 else if (*it == "*")
320 {
321 next = TemplateOperation::multiplication;
322 }
323 else if (*it == R"(%)")
324 {
325 next = TemplateOperation::modulo;
326 }
327 else if (*it == R"(/)")
328 {
329 next = TemplateOperation::division;
330 }
331 else
332 {
333 break;
334 }
335 }
336 else
337 {
338 int constant = 0;
339 try
340 {
341 constant = std::stoi(*it);
342 }
Patrick Williams83b1e9b2021-10-06 12:47:24 -0500343 catch (const std::invalid_argument&)
James Feist481c5d52019-08-13 14:40:40 -0700344 {
345 std::cerr << "Parameter not supported for templates "
346 << *it << "\n";
347 continue;
348 }
349 switch (next)
350 {
351 case TemplateOperation::addition:
352 {
353 number += constant;
354 break;
355 }
356 case TemplateOperation::subtraction:
357 {
358 number -= constant;
359 break;
360 }
361 case TemplateOperation::multiplication:
362 {
363 number *= constant;
364 break;
365 }
366 case TemplateOperation::division:
367 {
368 number /= constant;
369 break;
370 }
371 case TemplateOperation::modulo:
372 {
373 number = number % constant;
374 break;
375 }
376
377 default:
378 break;
379 }
380 }
381 isOperator = !isOperator;
382 }
James Feist35f5e0e2020-03-16 14:02:27 -0700383
James Feist481c5d52019-08-13 14:40:40 -0700384 std::string result = prefix + std::to_string(number);
385
James Feist35f5e0e2020-03-16 14:02:27 -0700386 std::string replaced(find.begin(), find.end());
387 for (auto it2 = split.begin(); it2 != split.end(); it2++)
388 {
389 replaced += " ";
390 replaced += *it2;
391 if (it2 == it)
392 {
393 break;
394 }
395 }
396 ret = replaced;
397
James Feist481c5d52019-08-13 14:40:40 -0700398 if (it != split.end())
399 {
400 for (; it != split.end(); it++)
401 {
402 result += " " + *it;
403 }
404 }
405 keyPair.value() = result;
406
Zhikui Rena0d1b3f2021-10-05 16:21:56 -0700407 // We probably just invalidated the pointer abovei,
408 // reset and continue to handle multiple templates
409 strPtr = keyPair.value().get_ptr<std::string*>();
410 if (strPtr == nullptr)
411 {
412 break;
413 }
James Feist481c5d52019-08-13 14:40:40 -0700414 }
415 }
416
417 strPtr = keyPair.value().get_ptr<std::string*>();
418 if (strPtr == nullptr)
419 {
James Feist35f5e0e2020-03-16 14:02:27 -0700420 return ret;
James Feist481c5d52019-08-13 14:40:40 -0700421 }
422
423 // convert hex numbers to ints
424 if (boost::starts_with(*strPtr, "0x"))
425 {
426 try
427 {
428 size_t pos = 0;
429 int64_t temp = std::stoul(*strPtr, &pos, 0);
430 if (pos == strPtr->size())
431 {
432 keyPair.value() = static_cast<uint64_t>(temp);
433 }
434 }
Patrick Williams83b1e9b2021-10-06 12:47:24 -0500435 catch (const std::invalid_argument&)
James Feist8c505da2020-05-28 10:06:33 -0700436 {}
Patrick Williams83b1e9b2021-10-06 12:47:24 -0500437 catch (const std::out_of_range&)
James Feist8c505da2020-05-28 10:06:33 -0700438 {}
James Feist481c5d52019-08-13 14:40:40 -0700439 }
440 // non-hex numbers
441 else
442 {
443 try
444 {
445 uint64_t temp = boost::lexical_cast<uint64_t>(*strPtr);
446 keyPair.value() = temp;
447 }
Patrick Williams83b1e9b2021-10-06 12:47:24 -0500448 catch (const boost::bad_lexical_cast&)
James Feist8c505da2020-05-28 10:06:33 -0700449 {}
James Feist481c5d52019-08-13 14:40:40 -0700450 }
James Feist35f5e0e2020-03-16 14:02:27 -0700451 return ret;
Xiang Liu2801a702020-01-20 14:29:34 -0800452}
Brad Bishop3cb8a602020-08-25 17:40:54 -0400453
Brad Bishop5d525412020-08-26 08:50:50 -0400454/// \brief JSON/DBus matching Callable for std::variant (visitor)
455///
456/// Default match JSON/DBus match implementation
457/// \tparam T The concrete DBus value type from BasicVariantType
458template <typename T>
459struct MatchProbe
460{
461 /// \param probe the probe statement to match against
462 /// \param value the property value being matched to a probe
463 /// \return true if the dbusValue matched the probe otherwise false
464 static bool match(const nlohmann::json& probe, const T& value)
465 {
466 return probe == value;
467 }
468};
469
470/// \brief JSON/DBus matching Callable for std::variant (visitor)
471///
472/// std::string specialization of MatchProbe enabling regex matching
473template <>
474struct MatchProbe<std::string>
475{
476 /// \param probe the probe statement to match against
477 /// \param value the string value being matched to a probe
478 /// \return true if the dbusValue matched the probe otherwise false
479 static bool match(const nlohmann::json& probe, const std::string& value)
480 {
481 if (probe.is_string())
482 {
483 try
484 {
485 std::regex search(probe);
486 std::smatch regMatch;
487 return std::regex_search(value, regMatch, search);
488 }
489 catch (const std::regex_error&)
490 {
491 std::cerr << "Syntax error in regular expression: " << probe
492 << " will never match";
493 }
494 }
495
496 // Skip calling nlohmann here, since it will never match a non-string
497 // to a std::string
498 return false;
499 }
500};
501
502/// \brief Forwarding JSON/DBus matching Callable for std::variant (visitor)
503///
504/// Forward calls to the correct template instantiation of MatchProbe
505struct MatchProbeForwarder
506{
507 explicit MatchProbeForwarder(const nlohmann::json& probe) : probeRef(probe)
508 {}
509 const nlohmann::json& probeRef;
510
511 template <typename T>
512 bool operator()(const T& dbusValue) const
513 {
514 return MatchProbe<T>::match(probeRef, dbusValue);
515 }
516};
517
Brad Bishop3cb8a602020-08-25 17:40:54 -0400518bool matchProbe(const nlohmann::json& probe, const BasicVariantType& dbusValue)
519{
Brad Bishop5d525412020-08-26 08:50:50 -0400520 return std::visit(MatchProbeForwarder(probe), dbusValue);
521}