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