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