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 
21 #include <regex>
22 #include <utility>
23 
24 constexpr const bool debug = false;
25 
26 // probes dbus interface dictionary for a key with a value that matches a regex
27 // When an interface passes a probe, also save its D-Bus path with it.
28 bool probeDbus(const std::string& interfaceName,
29                const std::map<std::string, nlohmann::json>& matches,
30                FoundDevices& devices, const std::shared_ptr<PerformScan>& scan,
31                bool& foundProbe)
32 {
33     bool foundMatch = false;
34     foundProbe = false;
35 
36     for (const auto& [path, interfaces] : scan->dbusProbeObjects)
37     {
38         auto it = interfaces.find(interfaceName);
39         if (it == interfaces.end())
40         {
41             continue;
42         }
43 
44         foundProbe = true;
45 
46         bool deviceMatches = true;
47         const DBusInterface& interface = it->second;
48 
49         for (const auto& [matchProp, matchJSON] : matches)
50         {
51             auto deviceValue = interface.find(matchProp);
52             if (deviceValue != interface.end())
53             {
54                 deviceMatches = deviceMatches &&
55                                 matchProbe(matchJSON, deviceValue->second);
56             }
57             else
58             {
59                 // Move on to the next DBus path
60                 deviceMatches = false;
61                 break;
62             }
63         }
64         if (deviceMatches)
65         {
66             if constexpr (debug)
67             {
68                 std::cerr << "probeDBus: Found probe match on " << path << " "
69                           << interfaceName << "\n";
70             }
71             devices.emplace_back(interface, path);
72             foundMatch = true;
73         }
74     }
75     return foundMatch;
76 }
77 
78 // default probe entry point, iterates a list looking for specific types to
79 // call specific probe functions
80 bool probe(const std::vector<std::string>& probeCommand,
81            const std::shared_ptr<PerformScan>& 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_type_codes lastCommand = probe_type_codes::FALSE_T;
89     bool first = true;
90 
91     for (const auto& probe : probeCommand)
92     {
93         FoundProbeTypeT probeType = findProbeType(probe);
94         if (probeType)
95         {
96             switch ((*probeType)->second)
97             {
98                 case probe_type_codes::FALSE_T:
99                 {
100                     cur = false;
101                     break;
102                 }
103                 case probe_type_codes::TRUE_T:
104                 {
105                     cur = true;
106                     break;
107                 }
108                 case 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_type_codes::AND:
117                   break;
118                 case probe_type_codes::OR:
119                   break;
120                   // these are no-ops until the last command switch
121                   */
122                 case 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_type_codes::AND)
179         {
180             ret = cur && ret;
181         }
182         else if (lastCommand == 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 ? (*probeType)->second
193                                 : probe_type_codes::FALSE_T;
194     }
195 
196     // probe passed, but empty device
197     if (ret && foundDevs.empty())
198     {
199         foundDevs.emplace_back(
200             boost::container::flat_map<std::string, DBusValueVariant>{},
201             std::string{});
202     }
203     if (matchOne && ret)
204     {
205         // match the last one
206         auto last = foundDevs.back();
207         foundDevs.clear();
208 
209         foundDevs.emplace_back(std::move(last));
210     }
211     return ret;
212 }
213 
214 PerformProbe::PerformProbe(nlohmann::json& recordRef,
215                            const std::vector<std::string>& probeCommand,
216                            std::string probeName,
217                            std::shared_ptr<PerformScan>& scanPtr) :
218     recordRef(recordRef), _probeCommand(probeCommand),
219     probeName(std::move(probeName)), scan(scanPtr)
220 {}
221 PerformProbe::~PerformProbe()
222 {
223     FoundDevices foundDevs;
224     if (probe(_probeCommand, scan, foundDevs))
225     {
226         scan->updateSystemConfiguration(recordRef, probeName, foundDevs);
227     }
228 }
229