xref: /openbmc/entity-manager/src/entity_manager/perform_probe.cpp (revision 8feb04544ae69154a47c4323f5ada2e6da34f50e)
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 <regex>
12 #include <utility>
13 
14 // probes dbus interface dictionary for a key with a value that matches a regex
15 // When an interface passes a probe, also save its D-Bus path with it.
16 bool probeDbus(const std::string& interfaceName,
17                const std::map<std::string, nlohmann::json>& matches,
18                scan::FoundDevices& devices,
19                const std::shared_ptr<scan::PerformScan>& scan, bool& foundProbe)
20 {
21     bool foundMatch = false;
22     foundProbe = false;
23 
24     for (const auto& [path, interfaces] : scan->dbusProbeObjects)
25     {
26         auto it = interfaces.find(interfaceName);
27         if (it == interfaces.end())
28         {
29             continue;
30         }
31 
32         foundProbe = true;
33 
34         bool deviceMatches = true;
35         const DBusInterface& interface = it->second;
36 
37         for (const auto& [matchProp, matchJSON] : matches)
38         {
39             auto deviceValue = interface.find(matchProp);
40             if (deviceValue != interface.end())
41             {
42                 deviceMatches = deviceMatches &&
43                                 matchProbe(matchJSON, deviceValue->second);
44             }
45             else
46             {
47                 // Move on to the next DBus path
48                 deviceMatches = false;
49                 break;
50             }
51         }
52         if (deviceMatches)
53         {
54             lg2::debug("Found probe match on {PATH} {IFACE}", "PATH", path,
55                        "IFACE", interfaceName);
56             devices.emplace_back(interface, path);
57             foundMatch = true;
58         }
59     }
60     return foundMatch;
61 }
62 
63 // default probe entry point, iterates a list looking for specific types to
64 // call specific probe functions
65 bool doProbe(const std::vector<std::string>& probeCommand,
66              const std::shared_ptr<scan::PerformScan>& scan,
67              scan::FoundDevices& foundDevs)
68 {
69     const static std::regex command(R"(\((.*)\))");
70     std::smatch match;
71     bool ret = false;
72     bool matchOne = false;
73     bool cur = true;
74     probe::probe_type_codes lastCommand = probe::probe_type_codes::FALSE_T;
75     bool first = true;
76 
77     for (const auto& probe : probeCommand)
78     {
79         probe::FoundProbeTypeT probeType = probe::findProbeType(probe);
80         if (probeType)
81         {
82             switch (*probeType)
83             {
84                 case probe::probe_type_codes::FALSE_T:
85                 {
86                     cur = false;
87                     break;
88                 }
89                 case probe::probe_type_codes::TRUE_T:
90                 {
91                     cur = true;
92                     break;
93                 }
94                 case probe::probe_type_codes::MATCH_ONE:
95                 {
96                     // set current value to last, this probe type shouldn't
97                     // affect the outcome
98                     cur = ret;
99                     matchOne = true;
100                     break;
101                 }
102                 /*case probe::probe_type_codes::AND:
103                   break;
104                 case probe::probe_type_codes::OR:
105                   break;
106                   // these are no-ops until the last command switch
107                   */
108                 case probe::probe_type_codes::FOUND:
109                 {
110                     if (!std::regex_search(probe, match, command))
111                     {
112                         lg2::error("found probe syntax error {JSON}", "JSON",
113                                    probe);
114                         return false;
115                     }
116                     std::string commandStr = *(match.begin() + 1);
117                     boost::replace_all(commandStr, "'", "");
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             boost::replace_all(commandStr, "'", "\"");
140             boost::replace_all(commandStr, R"(\)", R"(\\)");
141             auto json = nlohmann::json::parse(commandStr, nullptr, false, true);
142             if (json.is_discarded())
143             {
144                 lg2::error("dbus command syntax error {STR}", "STR",
145                            commandStr);
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 
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 
211 PerformProbe::~PerformProbe()
212 {
213     scan::FoundDevices foundDevs;
214     if (doProbe(_probeCommand, scan, foundDevs))
215     {
216         scan->updateSystemConfiguration(recordRef, probeName, foundDevs);
217     }
218 }
219 
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