xref: /openbmc/phosphor-objmgr/src/main.cpp (revision 11c0cc3cacc6fd0bf194e65435e4b1e4a88f6a96)
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