xref: /openbmc/entity-manager/src/entity_manager/perform_probe.cpp (revision 60618a73e852a6671fce0e879e7eac532313ccdb)
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