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