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 = std::find_if(endpoints.begin(), endpoints.end(), 325 [&assoc, &owner](const auto& endpoint) { 326 return (std::get<ownerPos>(endpoint) == owner) && 327 (std::get<assocPos>(endpoint) == assoc); 328 }); 329 if (e == endpoints.end()) 330 { 331 endpoints.emplace_back(owner, std::move(assoc)); 332 } 333 } 334 } 335 336 void removeFromPendingAssociations(const std::string& endpointPath, 337 AssociationMaps& assocMaps) 338 { 339 auto assoc = assocMaps.pending.begin(); 340 while (assoc != assocMaps.pending.end()) 341 { 342 auto endpoint = assoc->second.begin(); 343 while (endpoint != assoc->second.end()) 344 { 345 auto& e = std::get<assocPos>(*endpoint); 346 if (std::get<reversePathPos>(e) == endpointPath) 347 { 348 endpoint = assoc->second.erase(endpoint); 349 continue; 350 } 351 352 endpoint++; 353 } 354 355 if (assoc->second.empty()) 356 { 357 assoc = assocMaps.pending.erase(assoc); 358 continue; 359 } 360 361 assoc++; 362 } 363 } 364 365 void addSingleAssociation(boost::asio::io_context& io, 366 sdbusplus::asio::object_server& server, 367 const std::string& assocPath, 368 const std::string& endpoint, const std::string& owner, 369 const std::string& ownerPath, 370 AssociationMaps& assocMaps) 371 { 372 boost::container::flat_set<std::string> endpoints{endpoint}; 373 374 addEndpointsToAssocIfaces(io, server, assocPath, endpoints, assocMaps); 375 376 AssociationPaths objects; 377 boost::container::flat_set e{endpoint}; 378 objects.emplace(assocPath, e); 379 380 auto a = assocMaps.owners.find(ownerPath); 381 if (a != assocMaps.owners.end()) 382 { 383 auto o = a->second.find(owner); 384 if (o != a->second.end()) 385 { 386 auto p = o->second.find(assocPath); 387 if (p != o->second.end()) 388 { 389 p->second.emplace(endpoint); 390 } 391 else 392 { 393 o->second.emplace(assocPath, e); 394 } 395 } 396 else 397 { 398 a->second.emplace(owner, std::move(objects)); 399 } 400 } 401 else 402 { 403 boost::container::flat_map<std::string, AssociationPaths> owners; 404 owners.emplace(owner, std::move(objects)); 405 assocMaps.owners.emplace(endpoint, owners); 406 } 407 } 408 409 void checkIfPendingAssociation(boost::asio::io_context& io, 410 const std::string& objectPath, 411 const InterfaceMapType& interfaceMap, 412 AssociationMaps& assocMaps, 413 sdbusplus::asio::object_server& server) 414 { 415 auto pending = assocMaps.pending.find(objectPath); 416 if (pending == assocMaps.pending.end()) 417 { 418 return; 419 } 420 421 if (interfaceMap.find(objectPath) == interfaceMap.end()) 422 { 423 return; 424 } 425 426 auto endpoint = pending->second.begin(); 427 428 while (endpoint != pending->second.end()) 429 { 430 const auto& e = std::get<assocPos>(*endpoint); 431 432 // Ensure the other side of the association still exists 433 if (interfaceMap.find(std::get<reversePathPos>(e)) == 434 interfaceMap.end()) 435 { 436 endpoint++; 437 continue; 438 } 439 440 // Add both sides of the association: 441 // objectPath/forwardType and reversePath/reverseType 442 // 443 // The ownerPath is the reversePath - i.e. the endpoint that 444 // is on D-Bus and owns the org.openbmc.Associations iface. 445 // 446 const auto& ownerPath = std::get<reversePathPos>(e); 447 const auto& owner = std::get<ownerPos>(*endpoint); 448 449 auto assocPath = objectPath + '/' + std::get<forwardTypePos>(e); 450 auto endpointPath = ownerPath; 451 452 try 453 { 454 addSingleAssociation(io, server, assocPath, endpointPath, owner, 455 ownerPath, assocMaps); 456 457 // Now the reverse direction (still the same owner and ownerPath) 458 assocPath = endpointPath + '/' + std::get<reverseTypePos>(e); 459 endpointPath = objectPath; 460 addSingleAssociation(io, server, assocPath, endpointPath, owner, 461 ownerPath, assocMaps); 462 } 463 catch (const sdbusplus::exception_t& e) 464 { 465 // In some case the interface could not be created on DBus and an 466 // exception is thrown. mapper has no control of the interface/path 467 // of the associations, so it has to catch the error and drop the 468 // association request. 469 std::cerr << "Error adding association: assocPath " << assocPath 470 << ", endpointPath " << endpointPath 471 << ", what: " << e.what() << "\n"; 472 } 473 474 // Not pending anymore 475 endpoint = pending->second.erase(endpoint); 476 } 477 478 if (pending->second.empty()) 479 { 480 assocMaps.pending.erase(objectPath); 481 } 482 } 483 484 void findAssociations(const std::string& endpointPath, 485 AssociationMaps& assocMaps, 486 FindAssocResults& associationData) 487 { 488 for (const auto& [sourcePath, owners] : assocMaps.owners) 489 { 490 for (const auto& [owner, assocs] : owners) 491 { 492 for (const auto& [assocPath, endpoints] : assocs) 493 { 494 if (std::find(endpoints.begin(), endpoints.end(), 495 endpointPath) != endpoints.end()) 496 { 497 // assocPath is <path>/<type> which tells us what is on the 498 // other side of the association. 499 auto pos = assocPath.rfind('/'); 500 auto otherPath = assocPath.substr(0, pos); 501 auto otherType = assocPath.substr(pos + 1); 502 503 // Now we need to find the endpointPath/<type> -> 504 // [otherPath] entry so that we can get the type for 505 // endpointPath's side of the assoc. Do this by finding 506 // otherPath as an endpoint, and also checking for 507 // 'endpointPath/*' as the key. 508 auto a = std::find_if( 509 assocs.begin(), assocs.end(), 510 [&endpointPath, &otherPath](const auto& ap) { 511 const auto& endpoints = ap.second; 512 auto endpoint = std::find(endpoints.begin(), 513 endpoints.end(), otherPath); 514 if (endpoint != endpoints.end()) 515 { 516 return ap.first.starts_with(endpointPath + '/'); 517 } 518 return false; 519 }); 520 521 if (a != assocs.end()) 522 { 523 // Pull out the type from endpointPath/<type> 524 pos = a->first.rfind('/'); 525 auto thisType = a->first.substr(pos + 1); 526 527 // Now we know the full association: 528 // endpointPath/thisType -> otherPath/otherType 529 Association association{thisType, otherType, otherPath}; 530 associationData.emplace_back(owner, association); 531 } 532 } 533 } 534 } 535 } 536 } 537 538 /** @brief Remove an endpoint for a particular association from D-Bus. 539 * 540 * If the last endpoint is gone, remove the whole association interface, 541 * otherwise just update the D-Bus endpoints property. 542 * 543 * @param[in] assocPath - the association path 544 * @param[in] endpointPath - the endpoint path to find and remove 545 * @param[in,out] assocMaps - the association maps 546 * @param[in,out] server - sdbus system object 547 */ 548 void removeAssociationIfacesEntry(boost::asio::io_context& io, 549 const std::string& assocPath, 550 const std::string& endpointPath, 551 AssociationMaps& assocMaps, 552 sdbusplus::asio::object_server& server) 553 { 554 auto assoc = assocMaps.ifaces.find(assocPath); 555 if (assoc != assocMaps.ifaces.end()) 556 { 557 auto& endpoints = std::get<endpointsPos>(assoc->second); 558 auto e = std::find(endpoints.begin(), endpoints.end(), endpointPath); 559 if (e != endpoints.end()) 560 { 561 endpoints.erase(e); 562 563 scheduleUpdateEndpointsOnDbus(io, server, assocPath, assocMaps); 564 } 565 } 566 } 567 568 /** @brief Remove an endpoint from the association owners map. 569 * 570 * For a specific association path and owner, remove the endpoint. 571 * Remove all remaining artifacts of that endpoint in the owners map 572 * based on what frees up after the erase. 573 * 574 * @param[in] assocPath - the association object path 575 * @param[in] endpointPath - the endpoint object path 576 * @param[in] owner - the owner of the association 577 * @param[in,out] assocMaps - the association maps 578 */ 579 void removeAssociationOwnersEntry(const std::string& assocPath, 580 const std::string& endpointPath, 581 const std::string& owner, 582 AssociationMaps& assocMaps) 583 { 584 auto sources = assocMaps.owners.begin(); 585 while (sources != assocMaps.owners.end()) 586 { 587 auto owners = sources->second.find(owner); 588 if (owners != sources->second.end()) 589 { 590 auto entry = owners->second.find(assocPath); 591 if (entry != owners->second.end()) 592 { 593 auto e = std::find(entry->second.begin(), entry->second.end(), 594 endpointPath); 595 if (e != entry->second.end()) 596 { 597 entry->second.erase(e); 598 if (entry->second.empty()) 599 { 600 owners->second.erase(entry); 601 } 602 } 603 } 604 605 if (owners->second.empty()) 606 { 607 sources->second.erase(owners); 608 } 609 } 610 611 if (sources->second.empty()) 612 { 613 sources = assocMaps.owners.erase(sources); 614 continue; 615 } 616 sources++; 617 } 618 } 619 620 void moveAssociationToPending(boost::asio::io_context& io, 621 const std::string& endpointPath, 622 AssociationMaps& assocMaps, 623 sdbusplus::asio::object_server& server) 624 { 625 FindAssocResults associationData; 626 627 // Check which associations this path is an endpoint of, and 628 // then add them to the pending associations map and remove 629 // the associations objects. 630 findAssociations(endpointPath, assocMaps, associationData); 631 632 for (const auto& [owner, association] : associationData) 633 { 634 const auto& forwardPath = endpointPath; 635 const auto& forwardType = std::get<forwardTypePos>(association); 636 const auto& reversePath = std::get<reversePathPos>(association); 637 const auto& reverseType = std::get<reverseTypePos>(association); 638 639 addPendingAssociation(forwardPath, forwardType, reversePath, 640 reverseType, owner, assocMaps); 641 642 // Remove both sides of the association from assocMaps.ifaces 643 removeAssociationIfacesEntry(io, forwardPath + '/' + forwardType, 644 reversePath, assocMaps, server); 645 removeAssociationIfacesEntry(io, reversePath + '/' + reverseType, 646 forwardPath, assocMaps, server); 647 648 // Remove both sides of the association from assocMaps.owners 649 removeAssociationOwnersEntry(forwardPath + '/' + forwardType, 650 reversePath, owner, assocMaps); 651 removeAssociationOwnersEntry(reversePath + '/' + reverseType, 652 forwardPath, owner, assocMaps); 653 } 654 } 655