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