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             // Use emplace back when clang implements
72             // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0960r3.html
73             //
74             // https://en.cppreference.com/w/cpp/compiler_support/20
75             devices.push_back({interface, path});
76             foundMatch = true;
77         }
78     }
79     return foundMatch;
80 }
81 
82 // default probe entry point, iterates a list looking for specific types to
83 // call specific probe functions
84 bool probe(const std::vector<std::string>& probeCommand,
85            const std::shared_ptr<PerformScan>& scan, FoundDevices& foundDevs)
86 {
87     const static std::regex command(R"(\((.*)\))");
88     std::smatch match;
89     bool ret = false;
90     bool matchOne = false;
91     bool cur = true;
92     probe_type_codes lastCommand = probe_type_codes::FALSE_T;
93     bool first = true;
94 
95     for (const auto& probe : probeCommand)
96     {
97         FoundProbeTypeT probeType = findProbeType(probe);
98         if (probeType)
99         {
100             switch ((*probeType)->second)
101             {
102                 case probe_type_codes::FALSE_T:
103                 {
104                     cur = false;
105                     break;
106                 }
107                 case probe_type_codes::TRUE_T:
108                 {
109                     cur = true;
110                     break;
111                 }
112                 case probe_type_codes::MATCH_ONE:
113                 {
114                     // set current value to last, this probe type shouldn't
115                     // affect the outcome
116                     cur = ret;
117                     matchOne = true;
118                     break;
119                 }
120                 /*case probe_type_codes::AND:
121                   break;
122                 case probe_type_codes::OR:
123                   break;
124                   // these are no-ops until the last command switch
125                   */
126                 case probe_type_codes::FOUND:
127                 {
128                     if (!std::regex_search(probe, match, command))
129                     {
130                         std::cerr << "found probe syntax error " << probe
131                                   << "\n";
132                         return false;
133                     }
134                     std::string commandStr = *(match.begin() + 1);
135                     boost::replace_all(commandStr, "'", "");
136                     cur = (std::find(scan->passedProbes.begin(),
137                                      scan->passedProbes.end(),
138                                      commandStr) != scan->passedProbes.end());
139                     break;
140                 }
141                 default:
142                 {
143                     break;
144                 }
145             }
146         }
147         // look on dbus for object
148         else
149         {
150             if (!std::regex_search(probe, match, command))
151             {
152                 std::cerr << "dbus probe syntax error " << probe << "\n";
153                 return false;
154             }
155             std::string commandStr = *(match.begin() + 1);
156             // convert single ticks and single slashes into legal json
157             boost::replace_all(commandStr, "'", "\"");
158             boost::replace_all(commandStr, R"(\)", R"(\\)");
159             auto json = nlohmann::json::parse(commandStr, nullptr, false);
160             if (json.is_discarded())
161             {
162                 std::cerr << "dbus command syntax error " << commandStr << "\n";
163                 return false;
164             }
165             // we can match any (string, variant) property. (string, string)
166             // does a regex
167             std::map<std::string, nlohmann::json> dbusProbeMap =
168                 json.get<std::map<std::string, nlohmann::json>>();
169             auto findStart = probe.find('(');
170             if (findStart == std::string::npos)
171             {
172                 return false;
173             }
174             bool foundProbe = !!probeType;
175             std::string probeInterface = probe.substr(0, findStart);
176             cur = probeDbus(probeInterface, dbusProbeMap, foundDevs, scan,
177                             foundProbe);
178         }
179 
180         // some functions like AND and OR only take affect after the
181         // fact
182         if (lastCommand == probe_type_codes::AND)
183         {
184             ret = cur && ret;
185         }
186         else if (lastCommand == probe_type_codes::OR)
187         {
188             ret = cur || ret;
189         }
190 
191         if (first)
192         {
193             ret = cur;
194             first = false;
195         }
196         lastCommand = probeType ? (*probeType)->second
197                                 : probe_type_codes::FALSE_T;
198     }
199 
200     // probe passed, but empty device
201     if (ret && foundDevs.empty())
202     {
203         // Use emplace back when clang implements
204         // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0960r3.html
205         //
206         // https://en.cppreference.com/w/cpp/compiler_support/20
207         foundDevs.push_back(
208             {boost::container::flat_map<std::string, DBusValueVariant>{},
209              std::string{}});
210     }
211     if (matchOne && ret)
212     {
213         // match the last one
214         auto last = foundDevs.back();
215         foundDevs.clear();
216 
217         foundDevs.emplace_back(std::move(last));
218     }
219     return ret;
220 }
221 
222 PerformProbe::PerformProbe(nlohmann::json& recordRef,
223                            const std::vector<std::string>& probeCommand,
224                            std::string probeName,
225                            std::shared_ptr<PerformScan>& scanPtr) :
226     recordRef(recordRef),
227     _probeCommand(probeCommand), probeName(std::move(probeName)), scan(scanPtr)
228 {}
229 PerformProbe::~PerformProbe()
230 {
231     FoundDevices foundDevs;
232     if (probe(_probeCommand, scan, foundDevs))
233     {
234         scan->updateSystemConfiguration(recordRef, probeName, foundDevs);
235     }
236 }
237