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