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