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