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