xref: /openbmc/phosphor-objmgr/src/main.cpp (revision 3e2eb6af)
1 #include "associations.hpp"
2 #include "handler.hpp"
3 #include "processing.hpp"
4 #include "types.hpp"
5 
6 #include <tinyxml2.h>
7 
8 #include <boost/asio/io_context.hpp>
9 #include <boost/asio/signal_set.hpp>
10 #include <boost/container/flat_map.hpp>
11 #include <sdbusplus/asio/connection.hpp>
12 #include <sdbusplus/asio/object_server.hpp>
13 #include <xyz/openbmc_project/Common/error.hpp>
14 
15 #include <atomic>
16 #include <chrono>
17 #include <exception>
18 #include <iomanip>
19 #include <iostream>
20 #include <string>
21 #include <string_view>
22 #include <utility>
23 
24 AssociationMaps associationMaps;
25 
26 void updateOwners(sdbusplus::asio::connection* conn,
27                   boost::container::flat_map<std::string, std::string>& owners,
28                   const std::string& newObject)
29 {
30     if (newObject.starts_with(":"))
31     {
32         return;
33     }
34     conn->async_method_call(
35         [&, newObject](const boost::system::error_code ec,
36                        const std::string& nameOwner) {
37         if (ec)
38         {
39             std::cerr << "Error getting owner of " << newObject << " : " << ec
40                       << "\n";
41             return;
42         }
43         owners[nameOwner] = newObject;
44     },
45         "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetNameOwner",
46         newObject);
47 }
48 
49 void sendIntrospectionCompleteSignal(sdbusplus::asio::connection* systemBus,
50                                      const std::string& processName)
51 {
52     // TODO(ed) This signal doesn't get exposed properly in the
53     // introspect right now.  Find out how to register signals in
54     // sdbusplus
55     sdbusplus::message_t m = systemBus->new_signal(
56         "/xyz/openbmc_project/object_mapper",
57         "xyz.openbmc_project.ObjectMapper.Private", "IntrospectionComplete");
58     m.append(processName);
59     m.signal_send();
60 }
61 
62 struct InProgressIntrospect
63 {
64     InProgressIntrospect() = delete;
65     InProgressIntrospect(const InProgressIntrospect&) = delete;
66     InProgressIntrospect(InProgressIntrospect&&) = delete;
67     InProgressIntrospect& operator=(const InProgressIntrospect&) = delete;
68     InProgressIntrospect& operator=(InProgressIntrospect&&) = delete;
69     InProgressIntrospect(
70         sdbusplus::asio::connection* systemBus, boost::asio::io_context& io,
71         const std::string& processName, AssociationMaps& am
72 #ifdef MAPPER_ENABLE_DEBUG
73         ,
74         std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
75             globalStartTime
76 #endif
77         ) :
78         systemBus(systemBus),
79         io(io), processName(processName), assocMaps(am)
80 #ifdef MAPPER_ENABLE_DEBUG
81         ,
82         globalStartTime(std::move(globalStartTime)),
83         processStartTime(std::chrono::steady_clock::now())
84 #endif
85     {}
86     ~InProgressIntrospect()
87     {
88         try
89         {
90             sendIntrospectionCompleteSignal(systemBus, processName);
91 #ifdef MAPPER_ENABLE_DEBUG
92             std::chrono::duration<float> diff =
93                 std::chrono::steady_clock::now() - processStartTime;
94             std::cout << std::setw(50) << processName << " scan took "
95                       << diff.count() << " seconds\n";
96 
97             // If we're the last outstanding caller globally, calculate the
98             // time it took
99             if (globalStartTime != nullptr && globalStartTime.use_count() == 1)
100             {
101                 diff = std::chrono::steady_clock::now() - *globalStartTime;
102                 std::cout << "Total scan took " << diff.count()
103                           << " seconds to complete\n";
104             }
105 #endif
106         }
107         catch (const std::exception& e)
108         {
109             std::cerr
110                 << "Terminating, unhandled exception while introspecting: "
111                 << e.what() << "\n";
112             std::terminate();
113         }
114         catch (...)
115         {
116             std::cerr
117                 << "Terminating, unhandled exception while introspecting\n";
118             std::terminate();
119         }
120     }
121     sdbusplus::asio::connection* systemBus;
122     boost::asio::io_context& io;
123     std::string processName;
124     AssociationMaps& assocMaps;
125 #ifdef MAPPER_ENABLE_DEBUG
126     std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
127         globalStartTime;
128     std::chrono::time_point<std::chrono::steady_clock> processStartTime;
129 #endif
130 };
131 
132 void doAssociations(boost::asio::io_context& io,
133                     sdbusplus::asio::connection* systemBus,
134                     InterfaceMapType& interfaceMap,
135                     sdbusplus::asio::object_server& objectServer,
136                     const std::string& processName, const std::string& path,
137                     int timeoutRetries = 0)
138 {
139     constexpr int maxTimeoutRetries = 3;
140     systemBus->async_method_call(
141         [&io, &objectServer, path, processName, &interfaceMap, systemBus,
142          timeoutRetries](
143             const boost::system::error_code ec,
144             const std::variant<std::vector<Association>>& variantAssociations) {
145         if (ec)
146         {
147             if (ec.value() == boost::system::errc::timed_out &&
148                 timeoutRetries < maxTimeoutRetries)
149             {
150                 doAssociations(io, systemBus, interfaceMap, objectServer,
151                                processName, path, timeoutRetries + 1);
152                 return;
153             }
154             std::cerr << "Error getting associations from " << path << "\n";
155         }
156         std::vector<Association> associations =
157             std::get<std::vector<Association>>(variantAssociations);
158         associationChanged(io, objectServer, associations, path, processName,
159                            interfaceMap, associationMaps);
160     },
161         processName, path, "org.freedesktop.DBus.Properties", "Get",
162         assocDefsInterface, assocDefsProperty);
163 }
164 
165 void doIntrospect(boost::asio::io_context& io,
166                   sdbusplus::asio::connection* systemBus,
167                   const std::shared_ptr<InProgressIntrospect>& transaction,
168                   InterfaceMapType& interfaceMap,
169                   sdbusplus::asio::object_server& objectServer,
170                   const std::string& path, int timeoutRetries = 0)
171 {
172     constexpr int maxTimeoutRetries = 3;
173     systemBus->async_method_call(
174         [&io, &interfaceMap, &objectServer, transaction, path, systemBus,
175          timeoutRetries](const boost::system::error_code ec,
176                          const std::string& introspectXml) {
177         if (ec)
178         {
179             if (ec.value() == boost::system::errc::timed_out &&
180                 timeoutRetries < maxTimeoutRetries)
181             {
182                 doIntrospect(io, systemBus, transaction, interfaceMap,
183                              objectServer, path, timeoutRetries + 1);
184                 return;
185             }
186             std::cerr << "Introspect call failed with error: " << ec << ", "
187                       << ec.message()
188                       << " on process: " << transaction->processName
189                       << " path: " << path << "\n";
190             return;
191         }
192 
193         tinyxml2::XMLDocument doc;
194 
195         tinyxml2::XMLError e = doc.Parse(introspectXml.c_str());
196         if (e != tinyxml2::XMLError::XML_SUCCESS)
197         {
198             std::cerr << "XML parsing failed\n";
199             return;
200         }
201 
202         tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
203         if (pRoot == nullptr)
204         {
205             std::cerr << "XML document did not contain any data\n";
206             return;
207         }
208         auto& thisPathMap = interfaceMap[path];
209         tinyxml2::XMLElement* pElement = pRoot->FirstChildElement("interface");
210         while (pElement != nullptr)
211         {
212             const char* ifaceName = pElement->Attribute("name");
213             if (ifaceName == nullptr)
214             {
215                 continue;
216             }
217 
218             thisPathMap[transaction->processName].emplace(ifaceName);
219 
220             if (std::strcmp(ifaceName, assocDefsInterface) == 0)
221             {
222                 doAssociations(io, systemBus, interfaceMap, objectServer,
223                                transaction->processName, path);
224             }
225 
226             pElement = pElement->NextSiblingElement("interface");
227         }
228 
229         // Check if this new path has a pending association that can
230         // now be completed.
231         checkIfPendingAssociation(io, path, interfaceMap,
232                                   transaction->assocMaps, objectServer);
233 
234         pElement = pRoot->FirstChildElement("node");
235         while (pElement != nullptr)
236         {
237             const char* childPath = pElement->Attribute("name");
238             if (childPath != nullptr)
239             {
240                 std::string parentPath(path);
241                 if (parentPath == "/")
242                 {
243                     parentPath.clear();
244                 }
245 
246                 doIntrospect(io, systemBus, transaction, interfaceMap,
247                              objectServer, parentPath + "/" + childPath);
248             }
249             pElement = pElement->NextSiblingElement("node");
250         }
251     },
252         transaction->processName, path, "org.freedesktop.DBus.Introspectable",
253         "Introspect");
254 }
255 
256 void startNewIntrospect(
257     sdbusplus::asio::connection* systemBus, boost::asio::io_context& io,
258     InterfaceMapType& interfaceMap, const std::string& processName,
259     AssociationMaps& assocMaps,
260 #ifdef MAPPER_ENABLE_DEBUG
261     std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
262         globalStartTime,
263 #endif
264     sdbusplus::asio::object_server& objectServer)
265 {
266     if (needToIntrospect(processName))
267     {
268         std::shared_ptr<InProgressIntrospect> transaction =
269             std::make_shared<InProgressIntrospect>(systemBus, io, processName,
270                                                    assocMaps
271 #ifdef MAPPER_ENABLE_DEBUG
272                                                    ,
273                                                    globalStartTime
274 #endif
275             );
276 
277         doIntrospect(io, systemBus, transaction, interfaceMap, objectServer,
278                      "/");
279     }
280 }
281 
282 void doListNames(
283     boost::asio::io_context& io, InterfaceMapType& interfaceMap,
284     sdbusplus::asio::connection* systemBus,
285     boost::container::flat_map<std::string, std::string>& nameOwners,
286     AssociationMaps& assocMaps, sdbusplus::asio::object_server& objectServer)
287 {
288     systemBus->async_method_call(
289         [&io, &interfaceMap, &nameOwners, &objectServer, systemBus,
290          &assocMaps](const boost::system::error_code ec,
291                      std::vector<std::string> processNames) {
292         if (ec)
293         {
294             std::cerr << "Error getting names: " << ec << "\n";
295             std::exit(EXIT_FAILURE);
296             return;
297         }
298         // Try to make startup consistent
299         std::sort(processNames.begin(), processNames.end());
300 #ifdef MAPPER_ENABLE_DEBUG
301         std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
302             globalStartTime = std::make_shared<
303                 std::chrono::time_point<std::chrono::steady_clock>>(
304                 std::chrono::steady_clock::now());
305 #endif
306         for (const std::string& processName : processNames)
307         {
308             if (needToIntrospect(processName))
309             {
310                 startNewIntrospect(systemBus, io, interfaceMap, processName,
311                                    assocMaps,
312 #ifdef MAPPER_ENABLE_DEBUG
313                                    globalStartTime,
314 #endif
315                                    objectServer);
316                 updateOwners(systemBus, nameOwners, processName);
317             }
318         }
319     },
320         "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus",
321         "ListNames");
322 }
323 
324 // Remove parents of the passed in path that:
325 // 1) Only have the 3 default interfaces on them
326 //    - Means D-Bus created these, not application code,
327 //      with the Properties, Introspectable, and Peer ifaces
328 // 2) Have no other child for this owner
329 void removeUnneededParents(const std::string& objectPath,
330                            const std::string& owner,
331                            InterfaceMapType& interfaceMap)
332 {
333     auto parent = objectPath;
334 
335     while (true)
336     {
337         auto pos = parent.find_last_of('/');
338         if ((pos == std::string::npos) || (pos == 0))
339         {
340             break;
341         }
342         parent = parent.substr(0, pos);
343 
344         auto parentIt = interfaceMap.find(parent);
345         if (parentIt == interfaceMap.end())
346         {
347             break;
348         }
349 
350         auto ifacesIt = parentIt->second.find(owner);
351         if (ifacesIt == parentIt->second.end())
352         {
353             break;
354         }
355 
356         if (ifacesIt->second.size() != 3)
357         {
358             break;
359         }
360 
361         auto childPath = parent + '/';
362 
363         // Remove this parent if there isn't a remaining child on this owner
364         auto child = std::find_if(interfaceMap.begin(), interfaceMap.end(),
365                                   [&owner, &childPath](const auto& entry) {
366             return entry.first.starts_with(childPath) &&
367                    (entry.second.find(owner) != entry.second.end());
368         });
369 
370         if (child == interfaceMap.end())
371         {
372             parentIt->second.erase(ifacesIt);
373             if (parentIt->second.empty())
374             {
375                 interfaceMap.erase(parentIt);
376             }
377         }
378         else
379         {
380             break;
381         }
382     }
383 }
384 
385 int main()
386 {
387     boost::asio::io_context io;
388     std::shared_ptr<sdbusplus::asio::connection> systemBus =
389         std::make_shared<sdbusplus::asio::connection>(io);
390 
391     sdbusplus::asio::object_server server(systemBus);
392 
393     // Construct a signal set registered for process termination.
394     boost::asio::signal_set signals(io, SIGINT, SIGTERM);
395     signals.async_wait(
396         [&io](const boost::system::error_code&, int) { io.stop(); });
397 
398     InterfaceMapType interfaceMap;
399     boost::container::flat_map<std::string, std::string> nameOwners;
400 
401     auto nameChangeHandler = [&interfaceMap, &io, &nameOwners, &server,
402                               systemBus](sdbusplus::message_t& message) {
403         std::string name;     // well-known
404         std::string oldOwner; // unique-name
405         std::string newOwner; // unique-name
406 
407         message.read(name, oldOwner, newOwner);
408 
409         if (name.starts_with(':'))
410         {
411             // We should do nothing with unique-name connections.
412             return;
413         }
414 
415         if (!oldOwner.empty())
416         {
417             processNameChangeDelete(io, nameOwners, name, oldOwner,
418                                     interfaceMap, associationMaps, server);
419         }
420 
421         if (!newOwner.empty())
422         {
423 #ifdef MAPPER_ENABLE_DEBUG
424             auto transaction = std::make_shared<
425                 std::chrono::time_point<std::chrono::steady_clock>>(
426                 std::chrono::steady_clock::now());
427 #endif
428             // New daemon added
429             if (needToIntrospect(name))
430             {
431                 nameOwners[newOwner] = name;
432                 startNewIntrospect(systemBus.get(), io, interfaceMap, name,
433                                    associationMaps,
434 #ifdef MAPPER_ENABLE_DEBUG
435                                    transaction,
436 #endif
437                                    server);
438             }
439         }
440     };
441 
442     sdbusplus::bus::match_t nameOwnerChanged(
443         static_cast<sdbusplus::bus_t&>(*systemBus),
444         sdbusplus::bus::match::rules::nameOwnerChanged(),
445         std::move(nameChangeHandler));
446 
447     auto interfacesAddedHandler = [&io, &interfaceMap, &nameOwners,
448                                    &server](sdbusplus::message_t& message) {
449         sdbusplus::message::object_path objPath;
450         InterfacesAdded interfacesAdded;
451         message.read(objPath, interfacesAdded);
452         std::string wellKnown;
453         if (!getWellKnown(nameOwners, message.get_sender(), wellKnown))
454         {
455             return; // only introspect well-known
456         }
457         if (needToIntrospect(wellKnown))
458         {
459             processInterfaceAdded(io, interfaceMap, objPath, interfacesAdded,
460                                   wellKnown, associationMaps, server);
461         }
462     };
463 
464     sdbusplus::bus::match_t interfacesAdded(
465         static_cast<sdbusplus::bus_t&>(*systemBus),
466         sdbusplus::bus::match::rules::interfacesAdded(),
467         std::move(interfacesAddedHandler));
468 
469     auto interfacesRemovedHandler = [&io, &interfaceMap, &nameOwners,
470                                      &server](sdbusplus::message_t& message) {
471         sdbusplus::message::object_path objPath;
472         std::vector<std::string> interfacesRemoved;
473         message.read(objPath, interfacesRemoved);
474         auto connectionMap = interfaceMap.find(objPath.str);
475         if (connectionMap == interfaceMap.end())
476         {
477             return;
478         }
479 
480         std::string sender;
481         if (!getWellKnown(nameOwners, message.get_sender(), sender))
482         {
483             return;
484         }
485         for (const std::string& interface : interfacesRemoved)
486         {
487             auto interfaceSet = connectionMap->second.find(sender);
488             if (interfaceSet == connectionMap->second.end())
489             {
490                 continue;
491             }
492 
493             if (interface == assocDefsInterface)
494             {
495                 removeAssociation(io, objPath.str, sender, server,
496                                   associationMaps);
497             }
498 
499             interfaceSet->second.erase(interface);
500 
501             if (interfaceSet->second.empty())
502             {
503                 // If this was the last interface on this connection,
504                 // erase the connection
505                 connectionMap->second.erase(interfaceSet);
506 
507                 // Instead of checking if every single path is the endpoint
508                 // of an association that needs to be moved to pending,
509                 // only check when the only remaining owner of this path is
510                 // ourself, which would be because we still own the
511                 // association path.
512                 if ((connectionMap->second.size() == 1) &&
513                     (connectionMap->second.begin()->first ==
514                      "xyz.openbmc_project.ObjectMapper"))
515                 {
516                     // Remove the 2 association D-Bus paths and move the
517                     // association to pending.
518                     moveAssociationToPending(io, objPath.str, associationMaps,
519                                              server);
520                 }
521             }
522         }
523         // If this was the last connection on this object path,
524         // erase the object path
525         if (connectionMap->second.empty())
526         {
527             interfaceMap.erase(connectionMap);
528         }
529 
530         removeUnneededParents(objPath.str, sender, interfaceMap);
531     };
532 
533     sdbusplus::bus::match_t interfacesRemoved(
534         static_cast<sdbusplus::bus_t&>(*systemBus),
535         sdbusplus::bus::match::rules::interfacesRemoved(),
536         std::move(interfacesRemovedHandler));
537 
538     auto associationChangedHandler = [&io, &server, &nameOwners, &interfaceMap](
539                                          sdbusplus::message_t& message) {
540         std::string objectName;
541         boost::container::flat_map<std::string,
542                                    std::variant<std::vector<Association>>>
543             values;
544         message.read(objectName, values);
545         auto prop = values.find(assocDefsProperty);
546         if (prop != values.end())
547         {
548             std::vector<Association> associations =
549                 std::get<std::vector<Association>>(prop->second);
550 
551             std::string wellKnown;
552             if (!getWellKnown(nameOwners, message.get_sender(), wellKnown))
553             {
554                 return;
555             }
556             associationChanged(io, server, associations, message.get_path(),
557                                wellKnown, interfaceMap, associationMaps);
558         }
559     };
560     sdbusplus::bus::match_t assocChangedMatch(
561         static_cast<sdbusplus::bus_t&>(*systemBus),
562         sdbusplus::bus::match::rules::interface(
563             "org.freedesktop.DBus.Properties") +
564             sdbusplus::bus::match::rules::member("PropertiesChanged") +
565             sdbusplus::bus::match::rules::argN(0, assocDefsInterface),
566         std::move(associationChangedHandler));
567 
568     std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
569         server.add_interface("/xyz/openbmc_project/object_mapper",
570                              "xyz.openbmc_project.ObjectMapper");
571 
572     iface->register_method(
573         "GetAncestors", [&interfaceMap](std::string& reqPath,
574                                         std::vector<std::string>& interfaces) {
575         return getAncestors(interfaceMap, reqPath, interfaces);
576     });
577 
578     iface->register_method(
579         "GetObject", [&interfaceMap](const std::string& path,
580                                      std::vector<std::string>& interfaces) {
581         return getObject(interfaceMap, path, interfaces);
582     });
583 
584     iface->register_method(
585         "GetSubTree", [&interfaceMap](std::string& reqPath, int32_t depth,
586                                       std::vector<std::string>& interfaces) {
587         return getSubTree(interfaceMap, reqPath, depth, interfaces);
588     });
589 
590     iface->register_method(
591         "GetSubTreePaths",
592         [&interfaceMap](std::string& reqPath, int32_t depth,
593                         std::vector<std::string>& interfaces) {
594         return getSubTreePaths(interfaceMap, reqPath, depth, interfaces);
595     });
596 
597     iface->register_method(
598         "GetAssociatedSubTree",
599         [&interfaceMap](const sdbusplus::message::object_path& associationPath,
600                         const sdbusplus::message::object_path& reqPath,
601                         int32_t depth, std::vector<std::string>& interfaces) {
602         return getAssociatedSubTree(interfaceMap, associationMaps,
603                                     associationPath, reqPath, depth,
604                                     interfaces);
605     });
606 
607     iface->register_method(
608         "GetAssociatedSubTreePaths",
609         [&interfaceMap](const sdbusplus::message::object_path& associationPath,
610                         const sdbusplus::message::object_path& reqPath,
611                         int32_t depth, std::vector<std::string>& interfaces) {
612         return getAssociatedSubTreePaths(interfaceMap, associationMaps,
613                                          associationPath, reqPath, depth,
614                                          interfaces);
615     });
616 
617     iface->initialize();
618 
619     io.post([&]() {
620         doListNames(io, interfaceMap, systemBus.get(), nameOwners,
621                     associationMaps, server);
622     });
623 
624     systemBus->request_name("xyz.openbmc_project.ObjectMapper");
625 
626     io.run();
627 }
628