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