1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation 3 4 #include "perform_probe.hpp" 5 6 #include "perform_scan.hpp" 7 8 #include <boost/algorithm/string/replace.hpp> 9 #include <phosphor-logging/lg2.hpp> 10 11 #include <iostream> 12 #include <regex> 13 #include <utility> 14 15 // probes dbus interface dictionary for a key with a value that matches a regex 16 // When an interface passes a probe, also save its D-Bus path with it. 17 bool probeDbus(const std::string& interfaceName, 18 const std::map<std::string, nlohmann::json>& matches, 19 scan::FoundDevices& devices, 20 const std::shared_ptr<scan::PerformScan>& scan, bool& foundProbe) 21 { 22 bool foundMatch = false; 23 foundProbe = false; 24 25 for (const auto& [path, interfaces] : scan->dbusProbeObjects) 26 { 27 auto it = interfaces.find(interfaceName); 28 if (it == interfaces.end()) 29 { 30 continue; 31 } 32 33 foundProbe = true; 34 35 bool deviceMatches = true; 36 const DBusInterface& interface = it->second; 37 38 for (const auto& [matchProp, matchJSON] : matches) 39 { 40 auto deviceValue = interface.find(matchProp); 41 if (deviceValue != interface.end()) 42 { 43 deviceMatches = deviceMatches && 44 matchProbe(matchJSON, deviceValue->second); 45 } 46 else 47 { 48 // Move on to the next DBus path 49 deviceMatches = false; 50 break; 51 } 52 } 53 if (deviceMatches) 54 { 55 lg2::debug("Found probe match on {PATH} {IFACE}", "PATH", path, 56 "IFACE", interfaceName); 57 devices.emplace_back(interface, path); 58 foundMatch = true; 59 } 60 } 61 return foundMatch; 62 } 63 64 // default probe entry point, iterates a list looking for specific types to 65 // call specific probe functions 66 bool doProbe(const std::vector<std::string>& probeCommand, 67 const std::shared_ptr<scan::PerformScan>& scan, 68 scan::FoundDevices& foundDevs) 69 { 70 const static std::regex command(R"(\((.*)\))"); 71 std::smatch match; 72 bool ret = false; 73 bool matchOne = false; 74 bool cur = true; 75 probe::probe_type_codes lastCommand = probe::probe_type_codes::FALSE_T; 76 bool first = true; 77 78 for (const auto& probe : probeCommand) 79 { 80 probe::FoundProbeTypeT probeType = probe::findProbeType(probe); 81 if (probeType) 82 { 83 switch (*probeType) 84 { 85 case probe::probe_type_codes::FALSE_T: 86 { 87 cur = false; 88 break; 89 } 90 case probe::probe_type_codes::TRUE_T: 91 { 92 cur = true; 93 break; 94 } 95 case probe::probe_type_codes::MATCH_ONE: 96 { 97 // set current value to last, this probe type shouldn't 98 // affect the outcome 99 cur = ret; 100 matchOne = true; 101 break; 102 } 103 /*case probe::probe_type_codes::AND: 104 break; 105 case probe::probe_type_codes::OR: 106 break; 107 // these are no-ops until the last command switch 108 */ 109 case probe::probe_type_codes::FOUND: 110 { 111 if (!std::regex_search(probe, match, command)) 112 { 113 std::cerr 114 << "found probe syntax error " << probe << "\n"; 115 return false; 116 } 117 std::string commandStr = *(match.begin() + 1); 118 boost::replace_all(commandStr, "'", ""); 119 cur = (std::find(scan->passedProbes.begin(), 120 scan->passedProbes.end(), commandStr) != 121 scan->passedProbes.end()); 122 break; 123 } 124 default: 125 { 126 break; 127 } 128 } 129 } 130 // look on dbus for object 131 else 132 { 133 if (!std::regex_search(probe, match, command)) 134 { 135 std::cerr << "dbus probe syntax error " << probe << "\n"; 136 return false; 137 } 138 std::string commandStr = *(match.begin() + 1); 139 // convert single ticks and single slashes into legal json 140 boost::replace_all(commandStr, "'", "\""); 141 boost::replace_all(commandStr, R"(\)", R"(\\)"); 142 auto json = nlohmann::json::parse(commandStr, nullptr, false, true); 143 if (json.is_discarded()) 144 { 145 std::cerr << "dbus command syntax error " << commandStr << "\n"; 146 return false; 147 } 148 // we can match any (string, variant) property. (string, string) 149 // does a regex 150 std::map<std::string, nlohmann::json> dbusProbeMap = 151 json.get<std::map<std::string, nlohmann::json>>(); 152 auto findStart = probe.find('('); 153 if (findStart == std::string::npos) 154 { 155 return false; 156 } 157 bool foundProbe = !!probeType; 158 std::string probeInterface = probe.substr(0, findStart); 159 cur = probeDbus(probeInterface, dbusProbeMap, foundDevs, scan, 160 foundProbe); 161 } 162 163 // some functions like AND and OR only take affect after the 164 // fact 165 if (lastCommand == probe::probe_type_codes::AND) 166 { 167 ret = cur && ret; 168 } 169 else if (lastCommand == probe::probe_type_codes::OR) 170 { 171 ret = cur || ret; 172 } 173 174 if (first) 175 { 176 ret = cur; 177 first = false; 178 } 179 lastCommand = probeType.value_or(probe::probe_type_codes::FALSE_T); 180 } 181 182 // probe passed, but empty device 183 if (ret && foundDevs.empty()) 184 { 185 foundDevs.emplace_back( 186 boost::container::flat_map<std::string, DBusValueVariant>{}, 187 std::string{}); 188 } 189 if (matchOne && ret) 190 { 191 // match the last one 192 auto last = foundDevs.back(); 193 foundDevs.clear(); 194 195 foundDevs.emplace_back(std::move(last)); 196 } 197 return ret; 198 } 199 200 namespace probe 201 { 202 203 PerformProbe::PerformProbe(nlohmann::json& recordRef, 204 const std::vector<std::string>& probeCommand, 205 std::string probeName, 206 std::shared_ptr<scan::PerformScan>& scanPtr) : 207 recordRef(recordRef), _probeCommand(probeCommand), 208 probeName(std::move(probeName)), scan(scanPtr) 209 {} 210 211 PerformProbe::~PerformProbe() 212 { 213 scan::FoundDevices foundDevs; 214 if (doProbe(_probeCommand, scan, foundDevs)) 215 { 216 scan->updateSystemConfiguration(recordRef, probeName, foundDevs); 217 } 218 } 219 220 FoundProbeTypeT findProbeType(const std::string& probe) 221 { 222 static const boost::container::flat_map<const char*, probe_type_codes, 223 CmpStr> 224 probeTypes{{{"FALSE", probe_type_codes::FALSE_T}, 225 {"TRUE", probe_type_codes::TRUE_T}, 226 {"AND", probe_type_codes::AND}, 227 {"OR", probe_type_codes::OR}, 228 {"FOUND", probe_type_codes::FOUND}, 229 {"MATCH_ONE", probe_type_codes::MATCH_ONE}}}; 230 231 boost::container::flat_map<const char*, probe_type_codes, 232 CmpStr>::const_iterator probeType; 233 for (probeType = probeTypes.begin(); probeType != probeTypes.end(); 234 ++probeType) 235 { 236 if (probe.find(probeType->first) != std::string::npos) 237 { 238 return probeType->second; 239 } 240 } 241 242 return std::nullopt; 243 } 244 245 } // namespace probe 246