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 << " : " << ec 40 << "\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), 79 io(io), processName(processName), assocMaps(am) 80 #ifdef MAPPER_ENABLE_DEBUG 81 , 82 globalStartTime(std::move(globalStartTime)), 83 processStartTime(std::chrono::steady_clock::now()) 84 #endif 85 {} 86 ~InProgressIntrospect() 87 { 88 try 89 { 90 sendIntrospectionCompleteSignal(systemBus, processName); 91 #ifdef MAPPER_ENABLE_DEBUG 92 std::chrono::duration<float> diff = 93 std::chrono::steady_clock::now() - processStartTime; 94 std::cout << std::setw(50) << processName << " scan took " 95 << diff.count() << " seconds\n"; 96 97 // If we're the last outstanding caller globally, calculate the 98 // time it took 99 if (globalStartTime != nullptr && globalStartTime.use_count() == 1) 100 { 101 diff = std::chrono::steady_clock::now() - *globalStartTime; 102 std::cout << "Total scan took " << diff.count() 103 << " seconds to complete\n"; 104 } 105 #endif 106 } 107 catch (const std::exception& e) 108 { 109 std::cerr 110 << "Terminating, unhandled exception while introspecting: " 111 << e.what() << "\n"; 112 std::terminate(); 113 } 114 catch (...) 115 { 116 std::cerr 117 << "Terminating, unhandled exception while introspecting\n"; 118 std::terminate(); 119 } 120 } 121 sdbusplus::asio::connection* systemBus; 122 boost::asio::io_context& io; 123 std::string processName; 124 AssociationMaps& assocMaps; 125 #ifdef MAPPER_ENABLE_DEBUG 126 std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> 127 globalStartTime; 128 std::chrono::time_point<std::chrono::steady_clock> processStartTime; 129 #endif 130 }; 131 132 void doAssociations(boost::asio::io_context& io, 133 sdbusplus::asio::connection* systemBus, 134 InterfaceMapType& interfaceMap, 135 sdbusplus::asio::object_server& objectServer, 136 const std::string& processName, const std::string& path, 137 int timeoutRetries = 0) 138 { 139 constexpr int maxTimeoutRetries = 3; 140 systemBus->async_method_call( 141 [&io, &objectServer, path, processName, &interfaceMap, systemBus, 142 timeoutRetries]( 143 const boost::system::error_code ec, 144 const std::variant<std::vector<Association>>& variantAssociations) { 145 if (ec) 146 { 147 if (ec.value() == boost::system::errc::timed_out && 148 timeoutRetries < maxTimeoutRetries) 149 { 150 doAssociations(io, systemBus, interfaceMap, objectServer, 151 processName, path, timeoutRetries + 1); 152 return; 153 } 154 std::cerr << "Error getting associations from " << path << "\n"; 155 } 156 std::vector<Association> associations = 157 std::get<std::vector<Association>>(variantAssociations); 158 associationChanged(io, objectServer, associations, path, processName, 159 interfaceMap, associationMaps); 160 }, 161 processName, path, "org.freedesktop.DBus.Properties", "Get", 162 assocDefsInterface, assocDefsProperty); 163 } 164 165 void doIntrospect(boost::asio::io_context& io, 166 sdbusplus::asio::connection* systemBus, 167 const std::shared_ptr<InProgressIntrospect>& transaction, 168 InterfaceMapType& interfaceMap, 169 sdbusplus::asio::object_server& objectServer, 170 const std::string& path, int timeoutRetries = 0) 171 { 172 constexpr int maxTimeoutRetries = 3; 173 systemBus->async_method_call( 174 [&io, &interfaceMap, &objectServer, transaction, path, systemBus, 175 timeoutRetries](const boost::system::error_code ec, 176 const std::string& introspectXml) { 177 if (ec) 178 { 179 if (ec.value() == boost::system::errc::timed_out && 180 timeoutRetries < maxTimeoutRetries) 181 { 182 doIntrospect(io, systemBus, transaction, interfaceMap, 183 objectServer, path, timeoutRetries + 1); 184 return; 185 } 186 std::cerr << "Introspect call failed with error: " << ec << ", " 187 << ec.message() 188 << " on process: " << transaction->processName 189 << " path: " << path << "\n"; 190 return; 191 } 192 193 tinyxml2::XMLDocument doc; 194 195 tinyxml2::XMLError e = doc.Parse(introspectXml.c_str()); 196 if (e != tinyxml2::XMLError::XML_SUCCESS) 197 { 198 std::cerr << "XML parsing failed\n"; 199 return; 200 } 201 202 tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node"); 203 if (pRoot == nullptr) 204 { 205 std::cerr << "XML document did not contain any data\n"; 206 return; 207 } 208 auto& thisPathMap = interfaceMap[path]; 209 tinyxml2::XMLElement* pElement = 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>(systemBus, io, processName, 270 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(interfaceMap.begin(), interfaceMap.end(), 365 [&owner, &childPath](const auto& entry) { 366 return entry.first.starts_with(childPath) && 367 (entry.second.find(owner) != entry.second.end()); 368 }); 369 370 if (child == interfaceMap.end()) 371 { 372 parentIt->second.erase(ifacesIt); 373 if (parentIt->second.empty()) 374 { 375 interfaceMap.erase(parentIt); 376 } 377 } 378 else 379 { 380 break; 381 } 382 } 383 } 384 385 int main() 386 { 387 boost::asio::io_context io; 388 std::shared_ptr<sdbusplus::asio::connection> systemBus = 389 std::make_shared<sdbusplus::asio::connection>(io); 390 391 sdbusplus::asio::object_server server(systemBus); 392 393 // Construct a signal set registered for process termination. 394 boost::asio::signal_set signals(io, SIGINT, SIGTERM); 395 signals.async_wait( 396 [&io](const boost::system::error_code&, int) { io.stop(); }); 397 398 InterfaceMapType interfaceMap; 399 boost::container::flat_map<std::string, std::string> nameOwners; 400 401 auto nameChangeHandler = [&interfaceMap, &io, &nameOwners, &server, 402 systemBus](sdbusplus::message_t& message) { 403 std::string name; // well-known 404 std::string oldOwner; // unique-name 405 std::string newOwner; // unique-name 406 407 message.read(name, oldOwner, newOwner); 408 409 if (name.starts_with(':')) 410 { 411 // We should do nothing with unique-name connections. 412 return; 413 } 414 415 if (!oldOwner.empty()) 416 { 417 processNameChangeDelete(io, nameOwners, name, oldOwner, 418 interfaceMap, associationMaps, server); 419 } 420 421 if (!newOwner.empty()) 422 { 423 #ifdef MAPPER_ENABLE_DEBUG 424 auto transaction = std::make_shared< 425 std::chrono::time_point<std::chrono::steady_clock>>( 426 std::chrono::steady_clock::now()); 427 #endif 428 // New daemon added 429 if (needToIntrospect(name)) 430 { 431 nameOwners[newOwner] = name; 432 startNewIntrospect(systemBus.get(), io, interfaceMap, name, 433 associationMaps, 434 #ifdef MAPPER_ENABLE_DEBUG 435 transaction, 436 #endif 437 server); 438 } 439 } 440 }; 441 442 sdbusplus::bus::match_t nameOwnerChanged( 443 static_cast<sdbusplus::bus_t&>(*systemBus), 444 sdbusplus::bus::match::rules::nameOwnerChanged(), 445 std::move(nameChangeHandler)); 446 447 auto interfacesAddedHandler = [&io, &interfaceMap, &nameOwners, 448 &server](sdbusplus::message_t& message) { 449 sdbusplus::message::object_path objPath; 450 InterfacesAdded interfacesAdded; 451 message.read(objPath, interfacesAdded); 452 std::string wellKnown; 453 if (!getWellKnown(nameOwners, message.get_sender(), wellKnown)) 454 { 455 return; // only introspect well-known 456 } 457 if (needToIntrospect(wellKnown)) 458 { 459 processInterfaceAdded(io, interfaceMap, objPath, interfacesAdded, 460 wellKnown, associationMaps, server); 461 } 462 }; 463 464 sdbusplus::bus::match_t interfacesAdded( 465 static_cast<sdbusplus::bus_t&>(*systemBus), 466 sdbusplus::bus::match::rules::interfacesAdded(), 467 std::move(interfacesAddedHandler)); 468 469 auto interfacesRemovedHandler = [&io, &interfaceMap, &nameOwners, 470 &server](sdbusplus::message_t& message) { 471 sdbusplus::message::object_path objPath; 472 std::vector<std::string> interfacesRemoved; 473 message.read(objPath, interfacesRemoved); 474 auto connectionMap = interfaceMap.find(objPath.str); 475 if (connectionMap == interfaceMap.end()) 476 { 477 return; 478 } 479 480 std::string sender; 481 if (!getWellKnown(nameOwners, message.get_sender(), sender)) 482 { 483 return; 484 } 485 for (const std::string& interface : interfacesRemoved) 486 { 487 auto interfaceSet = connectionMap->second.find(sender); 488 if (interfaceSet == connectionMap->second.end()) 489 { 490 continue; 491 } 492 493 if (interface == assocDefsInterface) 494 { 495 removeAssociation(io, objPath.str, sender, server, 496 associationMaps); 497 } 498 499 interfaceSet->second.erase(interface); 500 501 if (interfaceSet->second.empty()) 502 { 503 // If this was the last interface on this connection, 504 // erase the connection 505 connectionMap->second.erase(interfaceSet); 506 507 // Instead of checking if every single path is the endpoint 508 // of an association that needs to be moved to pending, 509 // only check when the only remaining owner of this path is 510 // ourself, which would be because we still own the 511 // association path. 512 if ((connectionMap->second.size() == 1) && 513 (connectionMap->second.begin()->first == 514 "xyz.openbmc_project.ObjectMapper")) 515 { 516 // Remove the 2 association D-Bus paths and move the 517 // association to pending. 518 moveAssociationToPending(io, objPath.str, associationMaps, 519 server); 520 } 521 } 522 } 523 // If this was the last connection on this object path, 524 // erase the object path 525 if (connectionMap->second.empty()) 526 { 527 interfaceMap.erase(connectionMap); 528 } 529 530 removeUnneededParents(objPath.str, sender, interfaceMap); 531 }; 532 533 sdbusplus::bus::match_t interfacesRemoved( 534 static_cast<sdbusplus::bus_t&>(*systemBus), 535 sdbusplus::bus::match::rules::interfacesRemoved(), 536 std::move(interfacesRemovedHandler)); 537 538 auto associationChangedHandler = [&io, &server, &nameOwners, &interfaceMap]( 539 sdbusplus::message_t& message) { 540 std::string objectName; 541 boost::container::flat_map<std::string, 542 std::variant<std::vector<Association>>> 543 values; 544 message.read(objectName, values); 545 auto prop = values.find(assocDefsProperty); 546 if (prop != values.end()) 547 { 548 std::vector<Association> associations = 549 std::get<std::vector<Association>>(prop->second); 550 551 std::string wellKnown; 552 if (!getWellKnown(nameOwners, message.get_sender(), wellKnown)) 553 { 554 return; 555 } 556 associationChanged(io, server, associations, message.get_path(), 557 wellKnown, interfaceMap, associationMaps); 558 } 559 }; 560 sdbusplus::bus::match_t assocChangedMatch( 561 static_cast<sdbusplus::bus_t&>(*systemBus), 562 sdbusplus::bus::match::rules::interface( 563 "org.freedesktop.DBus.Properties") + 564 sdbusplus::bus::match::rules::member("PropertiesChanged") + 565 sdbusplus::bus::match::rules::argN(0, assocDefsInterface), 566 std::move(associationChangedHandler)); 567 568 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = 569 server.add_interface("/xyz/openbmc_project/object_mapper", 570 "xyz.openbmc_project.ObjectMapper"); 571 572 iface->register_method( 573 "GetAncestors", [&interfaceMap](std::string& reqPath, 574 std::vector<std::string>& interfaces) { 575 return getAncestors(interfaceMap, reqPath, interfaces); 576 }); 577 578 iface->register_method( 579 "GetObject", [&interfaceMap](const std::string& path, 580 std::vector<std::string>& interfaces) { 581 return getObject(interfaceMap, path, interfaces); 582 }); 583 584 iface->register_method( 585 "GetSubTree", [&interfaceMap](std::string& reqPath, int32_t depth, 586 std::vector<std::string>& interfaces) { 587 return getSubTree(interfaceMap, reqPath, depth, interfaces); 588 }); 589 590 iface->register_method( 591 "GetSubTreePaths", 592 [&interfaceMap](std::string& reqPath, int32_t depth, 593 std::vector<std::string>& interfaces) { 594 return getSubTreePaths(interfaceMap, reqPath, depth, interfaces); 595 }); 596 597 iface->register_method( 598 "GetAssociatedSubTree", 599 [&interfaceMap](const sdbusplus::message::object_path& associationPath, 600 const sdbusplus::message::object_path& reqPath, 601 int32_t depth, std::vector<std::string>& interfaces) { 602 return getAssociatedSubTree(interfaceMap, associationMaps, 603 associationPath, reqPath, depth, 604 interfaces); 605 }); 606 607 iface->register_method( 608 "GetAssociatedSubTreePaths", 609 [&interfaceMap](const sdbusplus::message::object_path& associationPath, 610 const sdbusplus::message::object_path& reqPath, 611 int32_t depth, std::vector<std::string>& interfaces) { 612 return getAssociatedSubTreePaths(interfaceMap, associationMaps, 613 associationPath, reqPath, depth, 614 interfaces); 615 }); 616 617 iface->initialize(); 618 619 io.post([&]() { 620 doListNames(io, interfaceMap, systemBus.get(), nameOwners, 621 associationMaps, server); 622 }); 623 624 systemBus->request_name("xyz.openbmc_project.ObjectMapper"); 625 626 io.run(); 627 } 628