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