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