1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
3
4 #include "perform_probe.hpp"
5
6 #include "perform_scan.hpp"
7
8 #include <phosphor-logging/lg2.hpp>
9
10 #include <regex>
11 #include <utility>
12
13 // probes dbus interface dictionary for a key with a value that matches a regex
14 // When an interface passes a probe, also save its D-Bus path with it.
probeDbus(const std::string & interfaceName,const std::map<std::string,nlohmann::json> & matches,scan::FoundDevices & devices,const std::shared_ptr<scan::PerformScan> & scan,bool & foundProbe)15 bool probeDbus(const std::string& interfaceName,
16 const std::map<std::string, nlohmann::json>& matches,
17 scan::FoundDevices& devices,
18 const std::shared_ptr<scan::PerformScan>& scan, bool& foundProbe)
19 {
20 bool foundMatch = false;
21 foundProbe = false;
22
23 for (const auto& [path, interfaces] : scan->dbusProbeObjects)
24 {
25 auto it = interfaces.find(interfaceName);
26 if (it == interfaces.end())
27 {
28 continue;
29 }
30
31 foundProbe = true;
32
33 bool deviceMatches = true;
34 const DBusInterface& interface = it->second;
35
36 for (const auto& [matchProp, matchJSON] : matches)
37 {
38 auto deviceValue = interface.find(matchProp);
39 if (deviceValue != interface.end())
40 {
41 deviceMatches = deviceMatches &&
42 matchProbe(matchJSON, deviceValue->second);
43 }
44 else
45 {
46 // Move on to the next DBus path
47 deviceMatches = false;
48 break;
49 }
50 }
51 if (deviceMatches)
52 {
53 lg2::debug("Found probe match on {PATH} {IFACE}", "PATH", path,
54 "IFACE", interfaceName);
55 devices.emplace_back(interface, path);
56 foundMatch = true;
57 }
58 }
59 return foundMatch;
60 }
61
62 // default probe entry point, iterates a list looking for specific types to
63 // call specific probe functions
doProbe(const std::vector<std::string> & probeCommand,const std::shared_ptr<scan::PerformScan> & scan,scan::FoundDevices & foundDevs)64 bool doProbe(const std::vector<std::string>& probeCommand,
65 const std::shared_ptr<scan::PerformScan>& scan,
66 scan::FoundDevices& foundDevs)
67 {
68 const static std::regex command(R"(\((.*)\))");
69 std::smatch match;
70 bool ret = false;
71 bool matchOne = false;
72 bool cur = true;
73 probe::probe_type_codes lastCommand = probe::probe_type_codes::FALSE_T;
74 bool first = true;
75
76 for (const auto& probe : probeCommand)
77 {
78 probe::FoundProbeTypeT probeType = probe::findProbeType(probe);
79 if (probeType)
80 {
81 switch (*probeType)
82 {
83 case probe::probe_type_codes::FALSE_T:
84 {
85 cur = false;
86 break;
87 }
88 case probe::probe_type_codes::TRUE_T:
89 {
90 cur = true;
91 break;
92 }
93 case probe::probe_type_codes::MATCH_ONE:
94 {
95 // set current value to last, this probe type shouldn't
96 // affect the outcome
97 cur = ret;
98 matchOne = true;
99 break;
100 }
101 /*case probe::probe_type_codes::AND:
102 break;
103 case probe::probe_type_codes::OR:
104 break;
105 // these are no-ops until the last command switch
106 */
107 case probe::probe_type_codes::FOUND:
108 {
109 if (!std::regex_search(probe, match, command))
110 {
111 lg2::error("found probe syntax error {JSON}", "JSON",
112 probe);
113 return false;
114 }
115 std::string commandStr = *(match.begin() + 1);
116 replaceAll(commandStr, "'", "");
117
118 cur = (std::find(scan->passedProbes.begin(),
119 scan->passedProbes.end(), commandStr) !=
120 scan->passedProbes.end());
121 break;
122 }
123 default:
124 {
125 break;
126 }
127 }
128 }
129 // look on dbus for object
130 else
131 {
132 if (!std::regex_search(probe, match, command))
133 {
134 lg2::error("dbus probe syntax error {JSON}", "JSON", probe);
135 return false;
136 }
137 std::string commandStr = *(match.begin() + 1);
138 // convert single ticks and single slashes into legal json
139 std::ranges::replace(commandStr, '\'', '"');
140
141 replaceAll(commandStr, R"(\)", R"(\\)");
142 auto json = nlohmann::json::parse(commandStr, nullptr, false, true);
143 if (json.is_discarded())
144 {
145 lg2::error("dbus command syntax error {STR}", "STR",
146 commandStr);
147 return false;
148 }
149 // we can match any (string, variant) property. (string, string)
150 // does a regex
151 std::map<std::string, nlohmann::json> dbusProbeMap =
152 json.get<std::map<std::string, nlohmann::json>>();
153 auto findStart = probe.find('(');
154 if (findStart == std::string::npos)
155 {
156 return false;
157 }
158 bool foundProbe = !!probeType;
159 std::string probeInterface = probe.substr(0, findStart);
160 cur = probeDbus(probeInterface, dbusProbeMap, foundDevs, scan,
161 foundProbe);
162 }
163
164 // some functions like AND and OR only take affect after the
165 // fact
166 if (lastCommand == probe::probe_type_codes::AND)
167 {
168 ret = cur && ret;
169 }
170 else if (lastCommand == probe::probe_type_codes::OR)
171 {
172 ret = cur || ret;
173 }
174
175 if (first)
176 {
177 ret = cur;
178 first = false;
179 }
180 lastCommand = probeType.value_or(probe::probe_type_codes::FALSE_T);
181 }
182
183 // probe passed, but empty device
184 if (ret && foundDevs.empty())
185 {
186 foundDevs.emplace_back(
187 boost::container::flat_map<std::string, DBusValueVariant>{},
188 std::string{});
189 }
190 if (matchOne && ret)
191 {
192 // match the last one
193 auto last = foundDevs.back();
194 foundDevs.clear();
195
196 foundDevs.emplace_back(std::move(last));
197 }
198 return ret;
199 }
200
201 namespace probe
202 {
203
PerformProbe(nlohmann::json & recordRef,const std::vector<std::string> & probeCommand,std::string probeName,std::shared_ptr<scan::PerformScan> & scanPtr)204 PerformProbe::PerformProbe(nlohmann::json& recordRef,
205 const std::vector<std::string>& probeCommand,
206 std::string probeName,
207 std::shared_ptr<scan::PerformScan>& scanPtr) :
208 recordRef(recordRef), _probeCommand(probeCommand),
209 probeName(std::move(probeName)), scan(scanPtr)
210 {}
211
~PerformProbe()212 PerformProbe::~PerformProbe()
213 {
214 scan::FoundDevices foundDevs;
215 if (doProbe(_probeCommand, scan, foundDevs))
216 {
217 scan->updateSystemConfiguration(recordRef, probeName, foundDevs);
218 }
219 }
220
findProbeType(const std::string & probe)221 FoundProbeTypeT findProbeType(const std::string& probe)
222 {
223 static const boost::container::flat_map<const char*, probe_type_codes,
224 CmpStr>
225 probeTypes{{{"FALSE", probe_type_codes::FALSE_T},
226 {"TRUE", probe_type_codes::TRUE_T},
227 {"AND", probe_type_codes::AND},
228 {"OR", probe_type_codes::OR},
229 {"FOUND", probe_type_codes::FOUND},
230 {"MATCH_ONE", probe_type_codes::MATCH_ONE}}};
231
232 boost::container::flat_map<const char*, probe_type_codes,
233 CmpStr>::const_iterator probeType;
234 for (probeType = probeTypes.begin(); probeType != probeTypes.end();
235 ++probeType)
236 {
237 if (probe.find(probeType->first) != std::string::npos)
238 {
239 return probeType->second;
240 }
241 }
242
243 return std::nullopt;
244 }
245
246 } // namespace probe
247