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