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* 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 {}
~InProgressIntrospectInProgressIntrospect87 ~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
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)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
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)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
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)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
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)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
removeUnneededParents(const std::string & objectPath,const std::string & owner,InterfaceMapType & interfaceMap)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
main()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