1 #include "associations.hpp" 2 #include "handler.hpp" 3 #include "processing.hpp" 4 #include "types.hpp" 5 6 #include <tinyxml2.h> 7 8 #include <boost/asio/io_context.hpp> 9 #include <boost/asio/signal_set.hpp> 10 #include <boost/container/flat_map.hpp> 11 #include <sdbusplus/asio/connection.hpp> 12 #include <sdbusplus/asio/object_server.hpp> 13 #include <xyz/openbmc_project/Common/error.hpp> 14 15 #include <atomic> 16 #include <chrono> 17 #include <exception> 18 #include <iomanip> 19 #include <iostream> 20 #include <string> 21 #include <string_view> 22 #include <utility> 23 24 AssociationMaps associationMaps; 25 26 void updateOwners(sdbusplus::asio::connection* conn, 27 boost::container::flat_map<std::string, std::string>& owners, 28 const std::string& newObject) 29 { 30 if (newObject.starts_with(":")) 31 { 32 return; 33 } 34 conn->async_method_call( 35 [&, newObject](const boost::system::error_code ec, 36 const std::string& nameOwner) { 37 if (ec) 38 { 39 std::cerr << "Error getting owner of " << newObject << " : " 40 << ec << "\n"; 41 return; 42 } 43 owners[nameOwner] = newObject; 44 }, 45 "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetNameOwner", 46 newObject); 47 } 48 49 void sendIntrospectionCompleteSignal(sdbusplus::asio::connection* systemBus, 50 const std::string& processName) 51 { 52 // TODO(ed) This signal doesn't get exposed properly in the 53 // introspect right now. Find out how to register signals in 54 // sdbusplus 55 sdbusplus::message_t m = systemBus->new_signal( 56 "/xyz/openbmc_project/object_mapper", 57 "xyz.openbmc_project.ObjectMapper.Private", "IntrospectionComplete"); 58 m.append(processName); 59 m.signal_send(); 60 } 61 62 struct InProgressIntrospect 63 { 64 InProgressIntrospect() = delete; 65 InProgressIntrospect(const InProgressIntrospect&) = delete; 66 InProgressIntrospect(InProgressIntrospect&&) = delete; 67 InProgressIntrospect& operator=(const InProgressIntrospect&) = delete; 68 InProgressIntrospect& operator=(InProgressIntrospect&&) = delete; 69 InProgressIntrospect( 70 sdbusplus::asio::connection* systemBusConnection, 71 boost::asio::io_context& ioContext, 72 const std::string& introspectProcessName, AssociationMaps& am 73 #ifdef MAPPER_ENABLE_DEBUG 74 , 75 std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> 76 globalIntrospectStartTime 77 #endif 78 ) : 79 systemBus(systemBusConnection), io(ioContext), 80 processName(introspectProcessName), assocMaps(am) 81 #ifdef MAPPER_ENABLE_DEBUG 82 , 83 globalStartTime(std::move(globalIntrospectStartTime)), 84 processStartTime(std::chrono::steady_clock::now()) 85 #endif 86 {} 87 ~InProgressIntrospect() 88 { 89 try 90 { 91 sendIntrospectionCompleteSignal(systemBus, processName); 92 #ifdef MAPPER_ENABLE_DEBUG 93 std::chrono::duration<float> diff = 94 std::chrono::steady_clock::now() - processStartTime; 95 std::cout << std::setw(50) << processName << " scan took " 96 << diff.count() << " seconds\n"; 97 98 // If we're the last outstanding caller globally, calculate the 99 // time it took 100 if (globalStartTime != nullptr && globalStartTime.use_count() == 1) 101 { 102 diff = std::chrono::steady_clock::now() - *globalStartTime; 103 std::cout << "Total scan took " << diff.count() 104 << " seconds to complete\n"; 105 } 106 #endif 107 } 108 catch (const std::exception& e) 109 { 110 std::cerr 111 << "Terminating, unhandled exception while introspecting: " 112 << e.what() << "\n"; 113 std::terminate(); 114 } 115 catch (...) 116 { 117 std::cerr 118 << "Terminating, unhandled exception while introspecting\n"; 119 std::terminate(); 120 } 121 } 122 sdbusplus::asio::connection* systemBus; 123 boost::asio::io_context& io; 124 std::string processName; 125 AssociationMaps& assocMaps; 126 #ifdef MAPPER_ENABLE_DEBUG 127 std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> 128 globalStartTime; 129 std::chrono::time_point<std::chrono::steady_clock> processStartTime; 130 #endif 131 }; 132 133 void doAssociations(boost::asio::io_context& io, 134 sdbusplus::asio::connection* systemBus, 135 InterfaceMapType& interfaceMap, 136 sdbusplus::asio::object_server& objectServer, 137 const std::string& processName, const std::string& path, 138 int timeoutRetries = 0) 139 { 140 constexpr int maxTimeoutRetries = 3; 141 systemBus->async_method_call( 142 [&io, &objectServer, path, processName, &interfaceMap, systemBus, 143 timeoutRetries]( 144 const boost::system::error_code ec, 145 const std::variant<std::vector<Association>>& variantAssociations) { 146 if (ec) 147 { 148 if (ec.value() == boost::system::errc::timed_out && 149 timeoutRetries < maxTimeoutRetries) 150 { 151 doAssociations(io, systemBus, interfaceMap, objectServer, 152 processName, path, timeoutRetries + 1); 153 return; 154 } 155 std::cerr << "Error getting associations from " << path << "\n"; 156 } 157 std::vector<Association> associations = 158 std::get<std::vector<Association>>(variantAssociations); 159 associationChanged(io, objectServer, associations, path, 160 processName, interfaceMap, associationMaps); 161 }, 162 processName, path, "org.freedesktop.DBus.Properties", "Get", 163 assocDefsInterface, assocDefsProperty); 164 } 165 166 void doIntrospect(boost::asio::io_context& io, 167 sdbusplus::asio::connection* systemBus, 168 const std::shared_ptr<InProgressIntrospect>& transaction, 169 InterfaceMapType& interfaceMap, 170 sdbusplus::asio::object_server& objectServer, 171 const std::string& path, int timeoutRetries = 0) 172 { 173 constexpr int maxTimeoutRetries = 3; 174 systemBus->async_method_call( 175 [&io, &interfaceMap, &objectServer, transaction, path, systemBus, 176 timeoutRetries](const boost::system::error_code ec, 177 const std::string& introspectXml) { 178 if (ec) 179 { 180 if (ec.value() == boost::system::errc::timed_out && 181 timeoutRetries < maxTimeoutRetries) 182 { 183 doIntrospect(io, systemBus, transaction, interfaceMap, 184 objectServer, path, timeoutRetries + 1); 185 return; 186 } 187 std::cerr << "Introspect call failed with error: " << ec << ", " 188 << ec.message() 189 << " on process: " << transaction->processName 190 << " path: " << path << "\n"; 191 return; 192 } 193 194 tinyxml2::XMLDocument doc; 195 196 tinyxml2::XMLError e = doc.Parse(introspectXml.c_str()); 197 if (e != tinyxml2::XMLError::XML_SUCCESS) 198 { 199 std::cerr << "XML parsing failed\n"; 200 return; 201 } 202 203 tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node"); 204 if (pRoot == nullptr) 205 { 206 std::cerr << "XML document did not contain any data\n"; 207 return; 208 } 209 auto& thisPathMap = interfaceMap[path]; 210 tinyxml2::XMLElement* pElement = 211 pRoot->FirstChildElement("interface"); 212 while (pElement != nullptr) 213 { 214 const char* ifaceName = pElement->Attribute("name"); 215 if (ifaceName == nullptr) 216 { 217 continue; 218 } 219 220 thisPathMap[transaction->processName].emplace(ifaceName); 221 222 if (std::strcmp(ifaceName, assocDefsInterface) == 0) 223 { 224 doAssociations(io, systemBus, interfaceMap, objectServer, 225 transaction->processName, path); 226 } 227 228 pElement = pElement->NextSiblingElement("interface"); 229 } 230 231 // Check if this new path has a pending association that can 232 // now be completed. 233 checkIfPendingAssociation(io, path, interfaceMap, 234 transaction->assocMaps, objectServer); 235 236 pElement = pRoot->FirstChildElement("node"); 237 while (pElement != nullptr) 238 { 239 const char* childPath = pElement->Attribute("name"); 240 if (childPath != nullptr) 241 { 242 std::string parentPath(path); 243 if (parentPath == "/") 244 { 245 parentPath.clear(); 246 } 247 248 doIntrospect(io, systemBus, transaction, interfaceMap, 249 objectServer, parentPath + "/" + childPath); 250 } 251 pElement = pElement->NextSiblingElement("node"); 252 } 253 }, 254 transaction->processName, path, "org.freedesktop.DBus.Introspectable", 255 "Introspect"); 256 } 257 258 void startNewIntrospect( 259 sdbusplus::asio::connection* systemBus, boost::asio::io_context& io, 260 InterfaceMapType& interfaceMap, const std::string& processName, 261 AssociationMaps& assocMaps, 262 #ifdef MAPPER_ENABLE_DEBUG 263 std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> 264 globalStartTime, 265 #endif 266 sdbusplus::asio::object_server& objectServer) 267 { 268 if (needToIntrospect(processName)) 269 { 270 std::shared_ptr<InProgressIntrospect> transaction = 271 std::make_shared<InProgressIntrospect>( 272 systemBus, io, processName, assocMaps 273 #ifdef MAPPER_ENABLE_DEBUG 274 , 275 globalStartTime 276 #endif 277 ); 278 279 doIntrospect(io, systemBus, transaction, interfaceMap, objectServer, 280 "/"); 281 } 282 } 283 284 void doListNames( 285 boost::asio::io_context& io, InterfaceMapType& interfaceMap, 286 sdbusplus::asio::connection* systemBus, 287 boost::container::flat_map<std::string, std::string>& nameOwners, 288 AssociationMaps& assocMaps, sdbusplus::asio::object_server& objectServer) 289 { 290 systemBus->async_method_call( 291 [&io, &interfaceMap, &nameOwners, &objectServer, systemBus, 292 &assocMaps](const boost::system::error_code ec, 293 std::vector<std::string> processNames) { 294 if (ec) 295 { 296 std::cerr << "Error getting names: " << ec << "\n"; 297 std::exit(EXIT_FAILURE); 298 return; 299 } 300 // Try to make startup consistent 301 std::sort(processNames.begin(), processNames.end()); 302 #ifdef MAPPER_ENABLE_DEBUG 303 std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> 304 globalStartTime = std::make_shared< 305 std::chrono::time_point<std::chrono::steady_clock>>( 306 std::chrono::steady_clock::now()); 307 #endif 308 for (const std::string& processName : processNames) 309 { 310 if (needToIntrospect(processName)) 311 { 312 startNewIntrospect(systemBus, io, interfaceMap, processName, 313 assocMaps, 314 #ifdef MAPPER_ENABLE_DEBUG 315 globalStartTime, 316 #endif 317 objectServer); 318 updateOwners(systemBus, nameOwners, processName); 319 } 320 } 321 }, 322 "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", 323 "ListNames"); 324 } 325 326 // Remove parents of the passed in path that: 327 // 1) Only have the 3 default interfaces on them 328 // - Means D-Bus created these, not application code, 329 // with the Properties, Introspectable, and Peer ifaces 330 // 2) Have no other child for this owner 331 void removeUnneededParents(const std::string& objectPath, 332 const std::string& owner, 333 InterfaceMapType& interfaceMap) 334 { 335 auto parent = objectPath; 336 337 while (true) 338 { 339 auto pos = parent.find_last_of('/'); 340 if ((pos == std::string::npos) || (pos == 0)) 341 { 342 break; 343 } 344 parent = parent.substr(0, pos); 345 346 auto parentIt = interfaceMap.find(parent); 347 if (parentIt == interfaceMap.end()) 348 { 349 break; 350 } 351 352 auto ifacesIt = parentIt->second.find(owner); 353 if (ifacesIt == parentIt->second.end()) 354 { 355 break; 356 } 357 358 if (ifacesIt->second.size() != 3) 359 { 360 break; 361 } 362 363 auto childPath = parent + '/'; 364 365 // Remove this parent if there isn't a remaining child on this owner 366 auto child = std::find_if( 367 interfaceMap.begin(), interfaceMap.end(), 368 [&owner, &childPath](const auto& entry) { 369 return entry.first.starts_with(childPath) && 370 (entry.second.find(owner) != entry.second.end()); 371 }); 372 373 if (child == interfaceMap.end()) 374 { 375 parentIt->second.erase(ifacesIt); 376 if (parentIt->second.empty()) 377 { 378 interfaceMap.erase(parentIt); 379 } 380 } 381 else 382 { 383 break; 384 } 385 } 386 } 387 388 int main() 389 { 390 boost::asio::io_context io; 391 std::shared_ptr<sdbusplus::asio::connection> systemBus = 392 std::make_shared<sdbusplus::asio::connection>(io); 393 394 sdbusplus::asio::object_server server(systemBus); 395 396 // Construct a signal set registered for process termination. 397 boost::asio::signal_set signals(io, SIGINT, SIGTERM); 398 signals.async_wait([&io](const boost::system::error_code&, int) { 399 io.stop(); 400 }); 401 402 InterfaceMapType interfaceMap; 403 boost::container::flat_map<std::string, std::string> nameOwners; 404 405 auto nameChangeHandler = [&interfaceMap, &io, &nameOwners, &server, 406 systemBus](sdbusplus::message_t& message) { 407 std::string name; // well-known 408 std::string oldOwner; // unique-name 409 std::string newOwner; // unique-name 410 411 message.read(name, oldOwner, newOwner); 412 413 if (name.starts_with(':')) 414 { 415 // We should do nothing with unique-name connections. 416 return; 417 } 418 419 if (!oldOwner.empty()) 420 { 421 processNameChangeDelete(io, nameOwners, name, oldOwner, 422 interfaceMap, associationMaps, server); 423 } 424 425 if (!newOwner.empty()) 426 { 427 #ifdef MAPPER_ENABLE_DEBUG 428 auto transaction = std::make_shared< 429 std::chrono::time_point<std::chrono::steady_clock>>( 430 std::chrono::steady_clock::now()); 431 #endif 432 // New daemon added 433 if (needToIntrospect(name)) 434 { 435 nameOwners[newOwner] = name; 436 startNewIntrospect(systemBus.get(), io, interfaceMap, name, 437 associationMaps, 438 #ifdef MAPPER_ENABLE_DEBUG 439 transaction, 440 #endif 441 server); 442 } 443 } 444 }; 445 446 sdbusplus::bus::match_t nameOwnerChanged( 447 static_cast<sdbusplus::bus_t&>(*systemBus), 448 sdbusplus::bus::match::rules::nameOwnerChanged(), 449 std::move(nameChangeHandler)); 450 451 auto interfacesAddedHandler = [&io, &interfaceMap, &nameOwners, 452 &server](sdbusplus::message_t& message) { 453 sdbusplus::message::object_path objPath; 454 InterfacesAdded interfacesAdded; 455 message.read(objPath, interfacesAdded); 456 std::string wellKnown; 457 if (!getWellKnown(nameOwners, message.get_sender(), wellKnown)) 458 { 459 return; // only introspect well-known 460 } 461 if (needToIntrospect(wellKnown)) 462 { 463 processInterfaceAdded(io, interfaceMap, objPath, interfacesAdded, 464 wellKnown, associationMaps, server); 465 } 466 }; 467 468 sdbusplus::bus::match_t interfacesAdded( 469 static_cast<sdbusplus::bus_t&>(*systemBus), 470 sdbusplus::bus::match::rules::interfacesAdded(), 471 std::move(interfacesAddedHandler)); 472 473 auto interfacesRemovedHandler = [&io, &interfaceMap, &nameOwners, 474 &server](sdbusplus::message_t& message) { 475 sdbusplus::message::object_path objPath; 476 std::vector<std::string> interfacesRemoved; 477 message.read(objPath, interfacesRemoved); 478 auto connectionMap = interfaceMap.find(objPath.str); 479 if (connectionMap == interfaceMap.end()) 480 { 481 return; 482 } 483 484 std::string sender; 485 if (!getWellKnown(nameOwners, message.get_sender(), sender)) 486 { 487 return; 488 } 489 for (const std::string& interface : interfacesRemoved) 490 { 491 auto interfaceSet = connectionMap->second.find(sender); 492 if (interfaceSet == connectionMap->second.end()) 493 { 494 continue; 495 } 496 497 if (interface == assocDefsInterface) 498 { 499 removeAssociation(io, objPath.str, sender, server, 500 associationMaps); 501 } 502 503 interfaceSet->second.erase(interface); 504 505 if (interfaceSet->second.empty()) 506 { 507 // If this was the last interface on this connection, 508 // erase the connection 509 connectionMap->second.erase(interfaceSet); 510 511 // Instead of checking if every single path is the endpoint 512 // of an association that needs to be moved to pending, 513 // only check when the only remaining owner of this path is 514 // ourself, which would be because we still own the 515 // association path. 516 if ((connectionMap->second.size() == 1) && 517 (connectionMap->second.begin()->first == 518 "xyz.openbmc_project.ObjectMapper")) 519 { 520 // Remove the 2 association D-Bus paths and move the 521 // association to pending. 522 moveAssociationToPending(io, objPath.str, associationMaps, 523 server); 524 } 525 } 526 } 527 // If this was the last connection on this object path, 528 // erase the object path 529 if (connectionMap->second.empty()) 530 { 531 interfaceMap.erase(connectionMap); 532 } 533 534 removeUnneededParents(objPath.str, sender, interfaceMap); 535 }; 536 537 sdbusplus::bus::match_t interfacesRemoved( 538 static_cast<sdbusplus::bus_t&>(*systemBus), 539 sdbusplus::bus::match::rules::interfacesRemoved(), 540 std::move(interfacesRemovedHandler)); 541 542 auto associationChangedHandler = [&io, &server, &nameOwners, &interfaceMap]( 543 sdbusplus::message_t& message) { 544 std::string objectName; 545 boost::container::flat_map<std::string, 546 std::variant<std::vector<Association>>> 547 values; 548 message.read(objectName, values); 549 auto prop = values.find(assocDefsProperty); 550 if (prop != values.end()) 551 { 552 std::vector<Association> associations = 553 std::get<std::vector<Association>>(prop->second); 554 555 std::string wellKnown; 556 if (!getWellKnown(nameOwners, message.get_sender(), wellKnown)) 557 { 558 return; 559 } 560 associationChanged(io, server, associations, message.get_path(), 561 wellKnown, interfaceMap, associationMaps); 562 } 563 }; 564 sdbusplus::bus::match_t assocChangedMatch( 565 static_cast<sdbusplus::bus_t&>(*systemBus), 566 sdbusplus::bus::match::rules::interface( 567 "org.freedesktop.DBus.Properties") + 568 sdbusplus::bus::match::rules::member("PropertiesChanged") + 569 sdbusplus::bus::match::rules::argN(0, assocDefsInterface), 570 std::move(associationChangedHandler)); 571 572 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = 573 server.add_interface("/xyz/openbmc_project/object_mapper", 574 "xyz.openbmc_project.ObjectMapper"); 575 576 iface->register_method( 577 "GetAncestors", [&interfaceMap](std::string& reqPath, 578 std::vector<std::string>& interfaces) { 579 return getAncestors(interfaceMap, reqPath, interfaces); 580 }); 581 582 iface->register_method( 583 "GetObject", [&interfaceMap](const std::string& path, 584 std::vector<std::string>& interfaces) { 585 return getObject(interfaceMap, path, interfaces); 586 }); 587 588 iface->register_method( 589 "GetSubTree", [&interfaceMap](std::string& reqPath, int32_t depth, 590 std::vector<std::string>& interfaces) { 591 return getSubTree(interfaceMap, reqPath, depth, interfaces); 592 }); 593 594 iface->register_method( 595 "GetSubTreePaths", 596 [&interfaceMap](std::string& reqPath, int32_t depth, 597 std::vector<std::string>& interfaces) { 598 return getSubTreePaths(interfaceMap, reqPath, depth, interfaces); 599 }); 600 601 iface->register_method( 602 "GetAssociatedSubTree", 603 [&interfaceMap](const sdbusplus::message::object_path& associationPath, 604 const sdbusplus::message::object_path& reqPath, 605 int32_t depth, std::vector<std::string>& interfaces) { 606 return getAssociatedSubTree(interfaceMap, associationMaps, 607 associationPath, reqPath, depth, 608 interfaces); 609 }); 610 611 iface->register_method( 612 "GetAssociatedSubTreePaths", 613 [&interfaceMap](const sdbusplus::message::object_path& associationPath, 614 const sdbusplus::message::object_path& reqPath, 615 int32_t depth, std::vector<std::string>& interfaces) { 616 return getAssociatedSubTreePaths(interfaceMap, associationMaps, 617 associationPath, reqPath, depth, 618 interfaces); 619 }); 620 621 iface->register_method( 622 "GetAssociatedSubTreeById", 623 [&interfaceMap](const std::string& id, const std::string& objectPath, 624 std::vector<std::string>& subtreeInterfaces, 625 const std::string& association, 626 std::vector<std::string>& endpointInterfaces) { 627 return getAssociatedSubTreeById(interfaceMap, associationMaps, id, 628 objectPath, subtreeInterfaces, 629 association, endpointInterfaces); 630 }); 631 632 iface->register_method( 633 "GetAssociatedSubTreePathsById", 634 [&interfaceMap](const std::string& id, const std::string& objectPath, 635 std::vector<std::string>& subtreeInterfaces, 636 const std::string& association, 637 std::vector<std::string>& endpointInterfaces) { 638 return getAssociatedSubTreePathsById( 639 interfaceMap, associationMaps, id, objectPath, 640 subtreeInterfaces, association, endpointInterfaces); 641 }); 642 643 iface->initialize(); 644 645 boost::asio::post(io, [&]() { 646 doListNames(io, interfaceMap, systemBus.get(), nameOwners, 647 associationMaps, server); 648 }); 649 650 systemBus->request_name("xyz.openbmc_project.ObjectMapper"); 651 652 io.run(); 653 } 654