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 <regex> 12 #include <utility> 13 14 // probes dbus interface dictionary for a key with a value that matches a regex 15 // When an interface passes a probe, also save its D-Bus path with it. 16 bool probeDbus(const std::string& interfaceName, 17 const std::map<std::string, nlohmann::json>& matches, 18 scan::FoundDevices& devices, 19 const std::shared_ptr<scan::PerformScan>& scan, bool& foundProbe) 20 { 21 bool foundMatch = false; 22 foundProbe = false; 23 24 for (const auto& [path, interfaces] : scan->dbusProbeObjects) 25 { 26 auto it = interfaces.find(interfaceName); 27 if (it == interfaces.end()) 28 { 29 continue; 30 } 31 32 foundProbe = true; 33 34 bool deviceMatches = true; 35 const DBusInterface& interface = it->second; 36 37 for (const auto& [matchProp, matchJSON] : matches) 38 { 39 auto deviceValue = interface.find(matchProp); 40 if (deviceValue != interface.end()) 41 { 42 deviceMatches = deviceMatches && 43 matchProbe(matchJSON, deviceValue->second); 44 } 45 else 46 { 47 // Move on to the next DBus path 48 deviceMatches = false; 49 break; 50 } 51 } 52 if (deviceMatches) 53 { 54 lg2::debug("Found probe match on {PATH} {IFACE}", "PATH", path, 55 "IFACE", interfaceName); 56 devices.emplace_back(interface, path); 57 foundMatch = true; 58 } 59 } 60 return foundMatch; 61 } 62 63 // default probe entry point, iterates a list looking for specific types to 64 // call specific probe functions 65 bool doProbe(const std::vector<std::string>& probeCommand, 66 const std::shared_ptr<scan::PerformScan>& scan, 67 scan::FoundDevices& foundDevs) 68 { 69 const static std::regex command(R"(\((.*)\))"); 70 std::smatch match; 71 bool ret = false; 72 bool matchOne = false; 73 bool cur = true; 74 probe::probe_type_codes lastCommand = probe::probe_type_codes::FALSE_T; 75 bool first = true; 76 77 for (const auto& probe : probeCommand) 78 { 79 probe::FoundProbeTypeT probeType = probe::findProbeType(probe); 80 if (probeType) 81 { 82 switch (*probeType) 83 { 84 case probe::probe_type_codes::FALSE_T: 85 { 86 cur = false; 87 break; 88 } 89 case probe::probe_type_codes::TRUE_T: 90 { 91 cur = true; 92 break; 93 } 94 case probe::probe_type_codes::MATCH_ONE: 95 { 96 // set current value to last, this probe type shouldn't 97 // affect the outcome 98 cur = ret; 99 matchOne = true; 100 break; 101 } 102 /*case probe::probe_type_codes::AND: 103 break; 104 case probe::probe_type_codes::OR: 105 break; 106 // these are no-ops until the last command switch 107 */ 108 case probe::probe_type_codes::FOUND: 109 { 110 if (!std::regex_search(probe, match, command)) 111 { 112 lg2::error("found probe syntax error {JSON}", "JSON", 113 probe); 114 return false; 115 } 116 std::string commandStr = *(match.begin() + 1); 117 boost::replace_all(commandStr, "'", ""); 118 cur = (std::find(scan->passedProbes.begin(), 119 scan->passedProbes.end(), commandStr) != 120 scan->passedProbes.end()); 121 break; 122 } 123 default: 124 { 125 break; 126 } 127 } 128 } 129 // look on dbus for object 130 else 131 { 132 if (!std::regex_search(probe, match, command)) 133 { 134 lg2::error("dbus probe syntax error {JSON}", "JSON", probe); 135 return false; 136 } 137 std::string commandStr = *(match.begin() + 1); 138 // convert single ticks and single slashes into legal json 139 boost::replace_all(commandStr, "'", "\""); 140 boost::replace_all(commandStr, R"(\)", R"(\\)"); 141 auto json = nlohmann::json::parse(commandStr, nullptr, false, true); 142 if (json.is_discarded()) 143 { 144 lg2::error("dbus command syntax error {STR}", "STR", 145 commandStr); 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