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