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