1 #include "associations.hpp" 2 3 #include <boost/algorithm/string/predicate.hpp> 4 #include <iostream> 5 6 void removeAssociation(const std::string& sourcePath, const std::string& owner, 7 sdbusplus::asio::object_server& server, 8 AssociationMaps& assocMaps) 9 { 10 // Use associationOwners to find the association paths and endpoints 11 // that the passed in object path and service own. Remove all of 12 // these endpoints from the actual association D-Bus objects, and if 13 // the endpoints property is then empty, the whole association object 14 // can be removed. Note there can be multiple services that own an 15 // association, and also that sourcePath is the path of the object 16 // that contains the org.openbmc.Associations interface and not the 17 // association path itself. 18 19 // Find the services that have associations for this object path 20 auto owners = assocMaps.owners.find(sourcePath); 21 if (owners == assocMaps.owners.end()) 22 { 23 return; 24 } 25 26 // Find the association paths and endpoints owned by this object 27 // path for this service. 28 auto assocs = owners->second.find(owner); 29 if (assocs == owners->second.end()) 30 { 31 return; 32 } 33 34 for (const auto& [assocPath, endpointsToRemove] : assocs->second) 35 { 36 removeAssociationEndpoints(server, assocPath, endpointsToRemove, 37 assocMaps); 38 } 39 40 // Remove the associationOwners entries for this owning path/service. 41 owners->second.erase(assocs); 42 if (owners->second.empty()) 43 { 44 assocMaps.owners.erase(owners); 45 } 46 47 // If we were still waiting on the other side of this association to 48 // show up, cancel that wait. 49 removeFromPendingAssociations(sourcePath, assocMaps); 50 } 51 52 void removeAssociationEndpoints( 53 sdbusplus::asio::object_server& objectServer, const std::string& assocPath, 54 const boost::container::flat_set<std::string>& endpointsToRemove, 55 AssociationMaps& assocMaps) 56 { 57 auto assoc = assocMaps.ifaces.find(assocPath); 58 if (assoc == assocMaps.ifaces.end()) 59 { 60 return; 61 } 62 63 auto& endpointsInDBus = std::get<endpointsPos>(assoc->second); 64 65 for (const auto& endpointToRemove : endpointsToRemove) 66 { 67 auto e = std::find(endpointsInDBus.begin(), endpointsInDBus.end(), 68 endpointToRemove); 69 70 if (e != endpointsInDBus.end()) 71 { 72 endpointsInDBus.erase(e); 73 } 74 } 75 76 if (endpointsInDBus.empty()) 77 { 78 objectServer.remove_interface(std::get<ifacePos>(assoc->second)); 79 std::get<ifacePos>(assoc->second) = nullptr; 80 std::get<endpointsPos>(assoc->second).clear(); 81 } 82 else 83 { 84 std::get<ifacePos>(assoc->second) 85 ->set_property("endpoints", endpointsInDBus); 86 } 87 } 88 89 void checkAssociationEndpointRemoves( 90 const std::string& sourcePath, const std::string& owner, 91 const AssociationPaths& newAssociations, 92 sdbusplus::asio::object_server& objectServer, AssociationMaps& assocMaps) 93 { 94 // Find the services that have associations on this path. 95 auto originalOwners = assocMaps.owners.find(sourcePath); 96 if (originalOwners == assocMaps.owners.end()) 97 { 98 return; 99 } 100 101 // Find the associations for this service 102 auto originalAssociations = originalOwners->second.find(owner); 103 if (originalAssociations == originalOwners->second.end()) 104 { 105 return; 106 } 107 108 // Compare the new endpoints versus the original endpoints, and 109 // remove any of the original ones that aren't in the new list. 110 for (const auto& [originalAssocPath, originalEndpoints] : 111 originalAssociations->second) 112 { 113 // Check if this source even still has each association that 114 // was there previously, and if not, remove all of its endpoints 115 // from the D-Bus endpoints property which will cause the whole 116 // association path to be removed if no endpoints remain. 117 auto newEndpoints = newAssociations.find(originalAssocPath); 118 if (newEndpoints == newAssociations.end()) 119 { 120 removeAssociationEndpoints(objectServer, originalAssocPath, 121 originalEndpoints, assocMaps); 122 } 123 else 124 { 125 // The association is still there. Check if the endpoints 126 // changed. 127 boost::container::flat_set<std::string> toRemove; 128 129 for (auto& originalEndpoint : originalEndpoints) 130 { 131 if (std::find(newEndpoints->second.begin(), 132 newEndpoints->second.end(), 133 originalEndpoint) == newEndpoints->second.end()) 134 { 135 toRemove.emplace(originalEndpoint); 136 } 137 } 138 if (!toRemove.empty()) 139 { 140 removeAssociationEndpoints(objectServer, originalAssocPath, 141 toRemove, assocMaps); 142 } 143 } 144 } 145 } 146 147 void addEndpointsToAssocIfaces( 148 sdbusplus::asio::object_server& objectServer, const std::string& assocPath, 149 const boost::container::flat_set<std::string>& endpointPaths, 150 AssociationMaps& assocMaps) 151 { 152 auto& iface = assocMaps.ifaces[assocPath]; 153 auto& i = std::get<ifacePos>(iface); 154 auto& endpoints = std::get<endpointsPos>(iface); 155 156 // Only add new endpoints 157 for (auto& e : endpointPaths) 158 { 159 if (std::find(endpoints.begin(), endpoints.end(), e) == endpoints.end()) 160 { 161 endpoints.push_back(e); 162 } 163 } 164 165 // If the interface already exists, only need to update 166 // the property value, otherwise create it 167 if (i) 168 { 169 i->set_property("endpoints", endpoints); 170 } 171 else 172 { 173 i = objectServer.add_interface(assocPath, XYZ_ASSOCIATION_INTERFACE); 174 i->register_property("endpoints", endpoints); 175 i->initialize(); 176 } 177 } 178 179 void associationChanged(sdbusplus::asio::object_server& objectServer, 180 const std::vector<Association>& associations, 181 const std::string& path, const std::string& owner, 182 const interface_map_type& interfaceMap, 183 AssociationMaps& assocMaps) 184 { 185 AssociationPaths objects; 186 187 for (const Association& association : associations) 188 { 189 std::string forward; 190 std::string reverse; 191 std::string endpoint; 192 std::tie(forward, reverse, endpoint) = association; 193 194 if (endpoint.empty()) 195 { 196 std::cerr << "Found invalid association on path " << path << "\n"; 197 continue; 198 } 199 200 // Can't create this association if the endpoint isn't on D-Bus. 201 if (interfaceMap.find(endpoint) == interfaceMap.end()) 202 { 203 addPendingAssociation(endpoint, reverse, path, forward, owner, 204 assocMaps); 205 continue; 206 } 207 208 if (forward.size()) 209 { 210 objects[path + "/" + forward].emplace(endpoint); 211 } 212 if (reverse.size()) 213 { 214 objects[endpoint + "/" + reverse].emplace(path); 215 } 216 } 217 for (const auto& object : objects) 218 { 219 addEndpointsToAssocIfaces(objectServer, object.first, object.second, 220 assocMaps); 221 } 222 223 // Check for endpoints being removed instead of added 224 checkAssociationEndpointRemoves(path, owner, objects, objectServer, 225 assocMaps); 226 227 if (!objects.empty()) 228 { 229 // Update associationOwners with the latest info 230 auto a = assocMaps.owners.find(path); 231 if (a != assocMaps.owners.end()) 232 { 233 auto o = a->second.find(owner); 234 if (o != a->second.end()) 235 { 236 o->second = std::move(objects); 237 } 238 else 239 { 240 a->second.emplace(owner, std::move(objects)); 241 } 242 } 243 else 244 { 245 boost::container::flat_map<std::string, AssociationPaths> owners; 246 owners.emplace(owner, std::move(objects)); 247 assocMaps.owners.emplace(path, owners); 248 } 249 } 250 } 251 252 void addPendingAssociation(const std::string& objectPath, 253 const std::string& type, 254 const std::string& endpointPath, 255 const std::string& endpointType, 256 const std::string& owner, AssociationMaps& assocMaps) 257 { 258 Association assoc{type, endpointType, endpointPath}; 259 260 auto p = assocMaps.pending.find(objectPath); 261 if (p == assocMaps.pending.end()) 262 { 263 ExistingEndpoints ee; 264 ee.emplace_back(owner, std::move(assoc)); 265 assocMaps.pending.emplace(objectPath, std::move(ee)); 266 } 267 else 268 { 269 // Already waiting on this path for another association, 270 // so just add this endpoint and owner. 271 auto& endpoints = p->second; 272 auto e = 273 std::find_if(endpoints.begin(), endpoints.end(), 274 [&assoc, &owner](const auto& endpoint) { 275 return (std::get<ownerPos>(endpoint) == owner) && 276 (std::get<assocPos>(endpoint) == assoc); 277 }); 278 if (e == endpoints.end()) 279 { 280 endpoints.emplace_back(owner, std::move(assoc)); 281 } 282 } 283 } 284 285 void removeFromPendingAssociations(const std::string& endpointPath, 286 AssociationMaps& assocMaps) 287 { 288 auto assoc = assocMaps.pending.begin(); 289 while (assoc != assocMaps.pending.end()) 290 { 291 auto endpoint = assoc->second.begin(); 292 while (endpoint != assoc->second.end()) 293 { 294 auto& e = std::get<assocPos>(*endpoint); 295 if (std::get<reversePathPos>(e) == endpointPath) 296 { 297 endpoint = assoc->second.erase(endpoint); 298 continue; 299 } 300 301 endpoint++; 302 } 303 304 if (assoc->second.empty()) 305 { 306 assoc = assocMaps.pending.erase(assoc); 307 continue; 308 } 309 310 assoc++; 311 } 312 } 313 314 void addSingleAssociation(sdbusplus::asio::object_server& server, 315 const std::string& assocPath, 316 const std::string& endpoint, const std::string& owner, 317 const std::string& ownerPath, 318 AssociationMaps& assocMaps) 319 { 320 boost::container::flat_set<std::string> endpoints{endpoint}; 321 322 addEndpointsToAssocIfaces(server, assocPath, endpoints, assocMaps); 323 324 AssociationPaths objects; 325 boost::container::flat_set e{endpoint}; 326 objects.emplace(assocPath, e); 327 328 auto a = assocMaps.owners.find(ownerPath); 329 if (a != assocMaps.owners.end()) 330 { 331 auto o = a->second.find(owner); 332 if (o != a->second.end()) 333 { 334 auto p = o->second.find(assocPath); 335 if (p != o->second.end()) 336 { 337 p->second.emplace(endpoint); 338 } 339 else 340 { 341 o->second.emplace(assocPath, e); 342 } 343 } 344 else 345 { 346 a->second.emplace(owner, std::move(objects)); 347 } 348 } 349 else 350 { 351 boost::container::flat_map<std::string, AssociationPaths> owners; 352 owners.emplace(owner, std::move(objects)); 353 assocMaps.owners.emplace(endpoint, owners); 354 } 355 } 356 357 void checkIfPendingAssociation(const std::string& objectPath, 358 const interface_map_type& interfaceMap, 359 AssociationMaps& assocMaps, 360 sdbusplus::asio::object_server& server) 361 { 362 auto pending = assocMaps.pending.find(objectPath); 363 if (pending == assocMaps.pending.end()) 364 { 365 return; 366 } 367 368 if (interfaceMap.find(objectPath) == interfaceMap.end()) 369 { 370 return; 371 } 372 373 auto endpoint = pending->second.begin(); 374 375 while (endpoint != pending->second.end()) 376 { 377 const auto& e = std::get<assocPos>(*endpoint); 378 379 // Ensure the other side of the association still exists 380 if (interfaceMap.find(std::get<reversePathPos>(e)) == 381 interfaceMap.end()) 382 { 383 endpoint++; 384 continue; 385 } 386 387 // Add both sides of the association: 388 // objectPath/forwardType and reversePath/reverseType 389 // 390 // The ownerPath is the reversePath - i.e. the endpoint that 391 // is on D-Bus and owns the org.openbmc.Associations iface. 392 // 393 const auto& ownerPath = std::get<reversePathPos>(e); 394 const auto& owner = std::get<ownerPos>(*endpoint); 395 396 auto assocPath = objectPath + '/' + std::get<forwardTypePos>(e); 397 auto endpointPath = ownerPath; 398 399 addSingleAssociation(server, assocPath, endpointPath, owner, ownerPath, 400 assocMaps); 401 402 // Now the reverse direction (still the same owner and ownerPath) 403 assocPath = endpointPath + '/' + std::get<reverseTypePos>(e); 404 endpointPath = objectPath; 405 addSingleAssociation(server, assocPath, endpointPath, owner, ownerPath, 406 assocMaps); 407 408 // Not pending anymore 409 endpoint = pending->second.erase(endpoint); 410 } 411 412 if (pending->second.empty()) 413 { 414 assocMaps.pending.erase(objectPath); 415 } 416 } 417 418 void findAssociations(const std::string& endpointPath, 419 AssociationMaps& assocMaps, 420 FindAssocResults& associationData) 421 { 422 for (const auto& [sourcePath, owners] : assocMaps.owners) 423 { 424 for (const auto& [owner, assocs] : owners) 425 { 426 for (const auto& [assocPath, endpoints] : assocs) 427 { 428 if (std::find(endpoints.begin(), endpoints.end(), 429 endpointPath) != endpoints.end()) 430 { 431 // assocPath is <path>/<type> which tells us what is on the 432 // other side of the association. 433 auto pos = assocPath.rfind('/'); 434 auto otherPath = assocPath.substr(0, pos); 435 auto otherType = assocPath.substr(pos + 1); 436 437 // Now we need to find the endpointPath/<type> -> 438 // [otherPath] entry so that we can get the type for 439 // endpointPath's side of the assoc. Do this by finding 440 // otherPath as an endpoint, and also checking for 441 // 'endpointPath/*' as the key. 442 auto a = std::find_if( 443 assocs.begin(), assocs.end(), 444 [&endpointPath, &otherPath](const auto& ap) { 445 const auto& endpoints = ap.second; 446 auto endpoint = std::find( 447 endpoints.begin(), endpoints.end(), otherPath); 448 if (endpoint != endpoints.end()) 449 { 450 return boost::starts_with(ap.first, 451 endpointPath + '/'); 452 } 453 return false; 454 }); 455 456 if (a != assocs.end()) 457 { 458 // Pull out the type from endpointPath/<type> 459 pos = a->first.rfind('/'); 460 auto thisType = a->first.substr(pos + 1); 461 462 // Now we know the full association: 463 // endpointPath/thisType -> otherPath/otherType 464 Association association{thisType, otherType, otherPath}; 465 associationData.emplace_back(owner, association); 466 } 467 } 468 } 469 } 470 } 471 } 472 473 /** @brief Remove an endpoint for a particular association from D-Bus. 474 * 475 * If the last endpoint is gone, remove the whole association interface, 476 * otherwise just update the D-Bus endpoints property. 477 * 478 * @param[in] assocPath - the association path 479 * @param[in] endpointPath - the endpoint path to find and remove 480 * @param[in,out] assocMaps - the association maps 481 * @param[in,out] server - sdbus system object 482 */ 483 void removeAssociationIfacesEntry(const std::string& assocPath, 484 const std::string& endpointPath, 485 AssociationMaps& assocMaps, 486 sdbusplus::asio::object_server& server) 487 { 488 auto assoc = assocMaps.ifaces.find(assocPath); 489 if (assoc != assocMaps.ifaces.end()) 490 { 491 auto& endpoints = std::get<endpointsPos>(assoc->second); 492 auto e = std::find(endpoints.begin(), endpoints.end(), endpointPath); 493 if (e != endpoints.end()) 494 { 495 endpoints.erase(e); 496 497 if (endpoints.empty()) 498 { 499 server.remove_interface(std::get<ifacePos>(assoc->second)); 500 std::get<ifacePos>(assoc->second) = nullptr; 501 } 502 else 503 { 504 std::get<ifacePos>(assoc->second) 505 ->set_property("endpoints", endpoints); 506 } 507 } 508 } 509 } 510 511 /** @brief Remove an endpoint from the association owners map. 512 * 513 * For a specific association path and owner, remove the endpoint. 514 * Remove all remaining artifacts of that endpoint in the owners map 515 * based on what frees up after the erase. 516 * 517 * @param[in] assocPath - the association object path 518 * @param[in] endpointPath - the endpoint object path 519 * @param[in] owner - the owner of the association 520 * @param[in,out] assocMaps - the association maps 521 * @param[in,out] server - sdbus system object 522 */ 523 void removeAssociationOwnersEntry(const std::string& assocPath, 524 const std::string& endpointPath, 525 const std::string& owner, 526 AssociationMaps& assocMaps, 527 sdbusplus::asio::object_server& server) 528 { 529 auto sources = assocMaps.owners.begin(); 530 while (sources != assocMaps.owners.end()) 531 { 532 auto owners = sources->second.find(owner); 533 if (owners != sources->second.end()) 534 { 535 auto entry = owners->second.find(assocPath); 536 if (entry != owners->second.end()) 537 { 538 auto e = std::find(entry->second.begin(), entry->second.end(), 539 endpointPath); 540 if (e != entry->second.end()) 541 { 542 entry->second.erase(e); 543 if (entry->second.empty()) 544 { 545 owners->second.erase(entry); 546 } 547 } 548 } 549 550 if (owners->second.empty()) 551 { 552 sources->second.erase(owners); 553 } 554 } 555 556 if (sources->second.empty()) 557 { 558 sources = assocMaps.owners.erase(sources); 559 continue; 560 } 561 sources++; 562 } 563 } 564 565 void moveAssociationToPending(const std::string& endpointPath, 566 AssociationMaps& assocMaps, 567 sdbusplus::asio::object_server& server) 568 { 569 FindAssocResults associationData; 570 571 // Check which associations this path is an endpoint of, and 572 // then add them to the pending associations map and remove 573 // the associations objects. 574 findAssociations(endpointPath, assocMaps, associationData); 575 576 for (const auto& [owner, association] : associationData) 577 { 578 const auto& forwardPath = endpointPath; 579 const auto& forwardType = std::get<forwardTypePos>(association); 580 const auto& reversePath = std::get<reversePathPos>(association); 581 const auto& reverseType = std::get<reverseTypePos>(association); 582 583 addPendingAssociation(forwardPath, forwardType, reversePath, 584 reverseType, owner, assocMaps); 585 586 // Remove both sides of the association from assocMaps.ifaces 587 removeAssociationIfacesEntry(forwardPath + '/' + forwardType, 588 reversePath, assocMaps, server); 589 removeAssociationIfacesEntry(reversePath + '/' + reverseType, 590 forwardPath, assocMaps, server); 591 592 // Remove both sides of the association from assocMaps.owners 593 removeAssociationOwnersEntry(forwardPath + '/' + forwardType, 594 reversePath, owner, assocMaps, server); 595 removeAssociationOwnersEntry(reversePath + '/' + reverseType, 596 forwardPath, owner, assocMaps, server); 597 } 598 } 599