xref: /openbmc/entity-manager/src/utils.cpp (revision ce8d1d0df3f40a48f8e5937ec21568bf0a435969)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2017 Intel Corporation
3 
4 #include "utils.hpp"
5 
6 #include <boost/algorithm/string/classification.hpp>
7 #include <boost/algorithm/string/replace.hpp>
8 #include <boost/algorithm/string/split.hpp>
9 #include <boost/container/flat_map.hpp>
10 #include <boost/lexical_cast.hpp>
11 #include <phosphor-logging/lg2.hpp>
12 #include <sdbusplus/bus/match.hpp>
13 
14 #include <algorithm>
15 #include <cctype>
16 #include <filesystem>
17 #include <map>
18 #include <ranges>
19 #include <regex>
20 #include <string_view>
21 #include <utility>
22 
23 namespace fs = std::filesystem;
24 
25 bool findFiles(const fs::path& dirPath, const std::string& matchString,
26                std::vector<fs::path>& foundPaths)
27 {
28     if (!fs::exists(dirPath))
29     {
30         return false;
31     }
32 
33     std::regex search(matchString);
34     std::smatch match;
35     for (const auto& p : fs::directory_iterator(dirPath))
36     {
37         std::string path = p.path().string();
38         if (std::regex_search(path, match, search))
39         {
40             foundPaths.emplace_back(p.path());
41         }
42     }
43     return true;
44 }
45 
46 bool findFiles(const std::vector<fs::path>&& dirPaths,
47                const std::string& matchString,
48                std::vector<fs::path>& foundPaths)
49 {
50     std::map<fs::path, fs::path> paths;
51     std::regex search(matchString);
52     std::smatch match;
53     for (const auto& dirPath : dirPaths)
54     {
55         if (!fs::exists(dirPath))
56         {
57             continue;
58         }
59 
60         for (const auto& p : fs::recursive_directory_iterator(dirPath))
61         {
62             std::error_code ec;
63             if (p.is_directory(ec))
64             {
65                 continue;
66             }
67 
68             std::string path = p.path().string();
69             if (std::regex_search(path, match, search))
70             {
71                 paths[p.path().filename()] = p.path();
72             }
73         }
74     }
75 
76     for (const auto& [key, value] : paths)
77     {
78         foundPaths.emplace_back(value);
79     }
80 
81     return !foundPaths.empty();
82 }
83 
84 bool getI2cDevicePaths(const fs::path& dirPath,
85                        boost::container::flat_map<size_t, fs::path>& busPaths)
86 {
87     if (!fs::exists(dirPath))
88     {
89         return false;
90     }
91 
92     // Regex for matching the path
93     std::regex searchPath(std::string(R"(i2c-\d+$)"));
94     // Regex for matching the bus numbers
95     std::regex searchBus(std::string(R"(\w[^-]*$)"));
96     std::smatch matchPath;
97     std::smatch matchBus;
98     for (const auto& p : fs::directory_iterator(dirPath))
99     {
100         std::string path = p.path().string();
101         if (std::regex_search(path, matchPath, searchPath))
102         {
103             if (std::regex_search(path, matchBus, searchBus))
104             {
105                 size_t bus = stoul(*matchBus.begin());
106                 busPaths.insert(std::pair<size_t, fs::path>(bus, p.path()));
107             }
108         }
109     }
110 
111     return true;
112 }
113 
114 /// \brief JSON/DBus matching Callable for std::variant (visitor)
115 ///
116 /// Default match JSON/DBus match implementation
117 /// \tparam T The concrete DBus value type from DBusValueVariant
118 template <typename T>
119 struct MatchProbe
120 {
121     /// \param probe the probe statement to match against
122     /// \param value the property value being matched to a probe
123     /// \return true if the dbusValue matched the probe otherwise false
124     static bool match(const nlohmann::json& probe, const T& value)
125     {
126         return probe == value;
127     }
128 };
129 
130 /// \brief JSON/DBus matching Callable for std::variant (visitor)
131 ///
132 /// std::string specialization of MatchProbe enabling regex matching
133 template <>
134 struct MatchProbe<std::string>
135 {
136     /// \param probe the probe statement to match against
137     /// \param value the string value being matched to a probe
138     /// \return true if the dbusValue matched the probe otherwise false
139     static bool match(const nlohmann::json& probe, const std::string& value)
140     {
141         if (probe.is_string())
142         {
143             try
144             {
145                 std::regex search(probe);
146                 std::smatch regMatch;
147                 return std::regex_search(value, regMatch, search);
148             }
149             catch (const std::regex_error&)
150             {
151                 lg2::error(
152                     "Syntax error in regular expression: {PROBE} will never match",
153                     "PROBE", probe);
154             }
155         }
156 
157         // Skip calling nlohmann here, since it will never match a non-string
158         // to a std::string
159         return false;
160     }
161 };
162 
163 /// \brief Forwarding JSON/DBus matching Callable for std::variant (visitor)
164 ///
165 /// Forward calls to the correct template instantiation of MatchProbe
166 struct MatchProbeForwarder
167 {
168     explicit MatchProbeForwarder(const nlohmann::json& probe) : probeRef(probe)
169     {}
170     const nlohmann::json& probeRef;
171 
172     template <typename T>
173     bool operator()(const T& dbusValue) const
174     {
175         return MatchProbe<T>::match(probeRef, dbusValue);
176     }
177 };
178 
179 bool matchProbe(const nlohmann::json& probe, const DBusValueVariant& dbusValue)
180 {
181     return std::visit(MatchProbeForwarder(probe), dbusValue);
182 }
183 
184 inline char asciiToLower(char c)
185 {
186     // Converts a character to lower case without relying on std::locale
187     if ('A' <= c && c <= 'Z')
188     {
189         c -= static_cast<char>('A' - 'a');
190     }
191     return c;
192 }
193 
194 std::pair<FirstIndex, LastIndex> iFindFirst(std::string_view str,
195                                             std::string_view sub)
196 {
197     if (sub.empty())
198     {
199         return {std::string_view::npos, std::string_view::npos};
200     }
201     auto result = std::ranges::search(str, sub, [](char a, char b) {
202         return asciiToLower(a) == asciiToLower(b);
203     });
204 
205     if (!result.empty())
206     {
207         size_t start = static_cast<size_t>(
208             std::ranges::distance(str.begin(), result.begin()));
209         return {start, start + sub.size()};
210     }
211 
212     return {std::string_view::npos, std::string_view::npos};
213 }
214