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
updateOwners(sdbusplus::asio::connection * conn,boost::container::flat_map<std::string,std::string> & owners,const std::string & newObject)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
sendIntrospectionCompleteSignal(sdbusplus::asio::connection * systemBus,const std::string & processName)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;
InProgressIntrospectInProgressIntrospect69 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 {}
~InProgressIntrospectInProgressIntrospect85 ~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
doAssociations(boost::asio::io_context & io,sdbusplus::asio::connection * systemBus,InterfaceMapType & interfaceMap,sdbusplus::asio::object_server & objectServer,const std::string & processName,const std::string & path,int timeoutRetries=0)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
doIntrospect(boost::asio::io_context & io,sdbusplus::asio::connection * systemBus,const std::shared_ptr<InProgressIntrospect> & transaction,InterfaceMapType & interfaceMap,sdbusplus::asio::object_server & objectServer,const std::string & path,int timeoutRetries=0)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
startNewIntrospect(sdbusplus::asio::connection * systemBus,boost::asio::io_context & io,InterfaceMapType & interfaceMap,const std::string & processName,AssociationMaps & assocMaps,std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> globalStartTime,sdbusplus::asio::object_server & objectServer)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
doListNames(boost::asio::io_context & io,InterfaceMapType & interfaceMap,sdbusplus::asio::connection * systemBus,boost::container::flat_map<std::string,std::string> & nameOwners,AssociationMaps & assocMaps,sdbusplus::asio::object_server & objectServer)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
removeUnneededParents(const std::string & objectPath,const std::string & owner,InterfaceMapType & interfaceMap)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
main()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