1 #include "association_manager.hpp" 2 3 #include <phosphor-logging/log.hpp> 4 5 #include <filesystem> 6 #include <fstream> 7 8 namespace phosphor 9 { 10 namespace inventory 11 { 12 namespace manager 13 { 14 namespace associations 15 { 16 using namespace phosphor::logging; 17 namespace fs = std::filesystem; 18 19 Manager::Manager(sdbusplus::bus::bus& bus, const std::string& jsonPath) : 20 _bus(bus), _jsonFile(jsonPath) 21 { 22 // If there aren't any conditional associations files, look for 23 // that default nonconditional one. 24 if (!loadConditions()) 25 { 26 if (fs::exists(_jsonFile)) 27 { 28 std::ifstream file{_jsonFile}; 29 auto json = nlohmann::json::parse(file, nullptr, true); 30 load(json); 31 } 32 } 33 } 34 35 /** 36 * @brief Throws an exception if 'num' is zero. Used for JSON 37 * sanity checking. 38 * 39 * @param[in] num - the number to check 40 */ 41 void throwIfZero(int num) 42 { 43 if (!num) 44 { 45 throw std::invalid_argument("Invalid empty field in JSON"); 46 } 47 } 48 49 bool Manager::loadConditions() 50 { 51 auto dir = _jsonFile.parent_path(); 52 53 for (const auto& dirent : fs::recursive_directory_iterator(dir)) 54 { 55 const auto& path = dirent.path(); 56 if (path.extension() == ".json") 57 { 58 std::ifstream file{path}; 59 auto json = nlohmann::json::parse(file, nullptr, true); 60 61 if (json.is_object() && json.contains("condition")) 62 { 63 const auto& conditionJSON = json.at("condition"); 64 if (!conditionJSON.contains("path") || 65 !conditionJSON.contains("interface") || 66 !conditionJSON.contains("property") || 67 !conditionJSON.contains("values")) 68 { 69 std::string msg = 70 "Invalid JSON in associations condition entry in " + 71 path.string() + ". Skipping file."; 72 log<level::ERR>(msg.c_str()); 73 continue; 74 } 75 76 Condition c; 77 c.file = path; 78 c.path = conditionJSON["path"].get<std::string>(); 79 if (c.path.front() != '/') 80 { 81 c.path = '/' + c.path; 82 } 83 fprintf(stderr, "found conditions file %s\n", c.file.c_str()); 84 c.interface = conditionJSON["interface"].get<std::string>(); 85 c.property = conditionJSON["property"].get<std::string>(); 86 87 // The values are in an array, and need to be 88 // converted to an InterfaceVariantType. 89 for (const auto& value : conditionJSON["values"]) 90 { 91 if (value.is_array()) 92 { 93 std::vector<uint8_t> variantValue; 94 for (const auto& v : value) 95 { 96 variantValue.push_back(v.get<uint8_t>()); 97 } 98 c.values.push_back(variantValue); 99 continue; 100 } 101 102 // Try the remaining types 103 auto s = value.get_ptr<const std::string*>(); 104 auto i = value.get_ptr<const int64_t*>(); 105 auto b = value.get_ptr<const bool*>(); 106 if (s) 107 { 108 c.values.push_back(*s); 109 } 110 else if (i) 111 { 112 c.values.push_back(*i); 113 } 114 else if (b) 115 { 116 c.values.push_back(*b); 117 } 118 else 119 { 120 std::stringstream ss; 121 ss << "Invalid condition property value in " << c.file 122 << ": " << value; 123 log<level::ERR>(ss.str().c_str()); 124 throw std::runtime_error(ss.str()); 125 } 126 } 127 128 _conditions.push_back(std::move(c)); 129 } 130 } 131 } 132 133 return !_conditions.empty(); 134 } 135 136 bool Manager::conditionMatch(const sdbusplus::message::object_path& objectPath, 137 const Object& object) 138 { 139 fs::path foundPath; 140 for (const auto& condition : _conditions) 141 { 142 if (condition.path != objectPath) 143 { 144 continue; 145 } 146 147 auto interface = std::find_if(object.begin(), object.end(), 148 [&condition](const auto& i) { 149 return i.first == condition.interface; 150 }); 151 if (interface == object.end()) 152 { 153 continue; 154 } 155 156 auto property = 157 std::find_if(interface->second.begin(), interface->second.end(), 158 [&condition](const auto& p) { 159 return condition.property == p.first; 160 }); 161 if (property == interface->second.end()) 162 { 163 continue; 164 } 165 166 auto match = std::find(condition.values.begin(), condition.values.end(), 167 property->second); 168 if (match != condition.values.end()) 169 { 170 foundPath = condition.file; 171 break; 172 } 173 } 174 175 if (!foundPath.empty()) 176 { 177 std::ifstream file{foundPath}; 178 auto json = nlohmann::json::parse(file, nullptr, true); 179 load(json["associations"]); 180 _conditions.clear(); 181 return true; 182 } 183 184 return false; 185 } 186 187 bool Manager::conditionMatch() 188 { 189 fs::path foundPath; 190 191 for (const auto& condition : _conditions) 192 { 193 // Compare the actualValue field against the values in the 194 // values vector to see if there is a condition match. 195 auto found = std::find(condition.values.begin(), condition.values.end(), 196 condition.actualValue); 197 if (found != condition.values.end()) 198 { 199 foundPath = condition.file; 200 break; 201 } 202 } 203 204 if (!foundPath.empty()) 205 { 206 std::ifstream file{foundPath}; 207 auto json = nlohmann::json::parse(file, nullptr, true); 208 load(json["associations"]); 209 _conditions.clear(); 210 return true; 211 } 212 213 return false; 214 } 215 216 void Manager::load(const nlohmann::json& json) 217 { 218 const std::string root{INVENTORY_ROOT}; 219 220 for (const auto& jsonAssoc : json) 221 { 222 // Only add the slash if necessary 223 std::string path = jsonAssoc.at("path"); 224 throwIfZero(path.size()); 225 if (path.front() != '/') 226 { 227 path = root + "/" + path; 228 } 229 else 230 { 231 path = root + path; 232 } 233 234 auto& assocEndpoints = _associations[path]; 235 236 for (const auto& endpoint : jsonAssoc.at("endpoints")) 237 { 238 std::string ftype = endpoint.at("types").at("fType"); 239 std::string rtype = endpoint.at("types").at("rType"); 240 throwIfZero(ftype.size()); 241 throwIfZero(rtype.size()); 242 Types types{std::move(ftype), std::move(rtype)}; 243 244 Paths paths = endpoint.at("paths"); 245 throwIfZero(paths.size()); 246 assocEndpoints.emplace_back(std::move(types), std::move(paths)); 247 } 248 } 249 } 250 251 void Manager::createAssociations(const std::string& objectPath, 252 bool deferSignal) 253 { 254 auto endpoints = _associations.find(objectPath); 255 if (endpoints == _associations.end()) 256 { 257 return; 258 } 259 260 if (std::find(_handled.begin(), _handled.end(), objectPath) != 261 _handled.end()) 262 { 263 return; 264 } 265 266 _handled.push_back(objectPath); 267 268 for (const auto& endpoint : endpoints->second) 269 { 270 const auto& types = std::get<typesPos>(endpoint); 271 const auto& paths = std::get<pathsPos>(endpoint); 272 273 for (const auto& endpointPath : paths) 274 { 275 const auto& forwardType = std::get<forwardTypePos>(types); 276 const auto& reverseType = std::get<reverseTypePos>(types); 277 278 createAssociation(objectPath, forwardType, endpointPath, 279 reverseType, deferSignal); 280 } 281 } 282 } 283 284 void Manager::createAssociation(const std::string& forwardPath, 285 const std::string& forwardType, 286 const std::string& reversePath, 287 const std::string& reverseType, 288 bool deferSignal) 289 { 290 auto object = _associationIfaces.find(forwardPath); 291 if (object == _associationIfaces.end()) 292 { 293 auto a = std::make_unique<AssociationObject>( 294 _bus, forwardPath.c_str(), AssociationObject::action::defer_emit); 295 296 using AssociationProperty = 297 std::vector<std::tuple<std::string, std::string, std::string>>; 298 AssociationProperty prop; 299 300 prop.emplace_back(forwardType, reverseType, reversePath); 301 a->associations(std::move(prop)); 302 if (!deferSignal) 303 { 304 a->emit_object_added(); 305 } 306 _associationIfaces.emplace(forwardPath, std::move(a)); 307 } 308 else 309 { 310 // Interface exists, just update the property 311 auto prop = object->second->associations(); 312 prop.emplace_back(forwardType, reverseType, reversePath); 313 object->second->associations(std::move(prop), deferSignal); 314 } 315 } 316 } // namespace associations 317 } // namespace manager 318 } // namespace inventory 319 } // namespace phosphor 320