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