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