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