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