xref: /openbmc/entity-manager/src/entity_manager/entity_manager.cpp (revision b7a74178ae21a8102566c8ee857e9721cfb22193)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
3 
4 #include "entity_manager.hpp"
5 
6 #include "../utils.hpp"
7 #include "../variant_visitors.hpp"
8 #include "configuration.hpp"
9 #include "dbus_interface.hpp"
10 #include "log_device_inventory.hpp"
11 #include "overlay.hpp"
12 #include "perform_scan.hpp"
13 #include "topology.hpp"
14 #include "utils.hpp"
15 
16 #include <boost/asio/io_context.hpp>
17 #include <boost/asio/post.hpp>
18 #include <boost/asio/steady_timer.hpp>
19 #include <boost/container/flat_set.hpp>
20 #include <boost/range/iterator_range.hpp>
21 #include <nlohmann/json.hpp>
22 #include <phosphor-logging/lg2.hpp>
23 #include <sdbusplus/asio/connection.hpp>
24 #include <sdbusplus/asio/object_server.hpp>
25 #include <xyz/openbmc_project/Association/Definitions/common.hpp>
26 #include <xyz/openbmc_project/Inventory/Item/Bmc/common.hpp>
27 #include <xyz/openbmc_project/Inventory/Item/System/common.hpp>
28 #include <xyz/openbmc_project/Inventory/Item/common.hpp>
29 
30 #include <filesystem>
31 #include <flat_map>
32 #include <fstream>
33 #include <functional>
34 #include <map>
35 #include <regex>
36 constexpr const char* tempConfigDir = "/tmp/configuration/";
37 constexpr const char* lastConfiguration = "/tmp/configuration/last.json";
38 
39 static constexpr std::array<const char*, 6> settableInterfaces = {
40     "FanProfile", "Pid", "Pid.Zone", "Stepwise", "Thresholds", "Polling"};
41 
42 const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]");
43 const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]");
44 
getPermission(const std::string & interface)45 sdbusplus::asio::PropertyPermission getPermission(const std::string& interface)
46 {
47     return std::find(settableInterfaces.begin(), settableInterfaces.end(),
48                      interface) != settableInterfaces.end()
49                ? sdbusplus::asio::PropertyPermission::readWrite
50                : sdbusplus::asio::PropertyPermission::readOnly;
51 }
52 
EntityManager(std::shared_ptr<sdbusplus::asio::connection> & systemBus,boost::asio::io_context & io,const std::vector<std::filesystem::path> & configurationDirectories,const std::filesystem::path & schemaDirectory)53 EntityManager::EntityManager(
54     std::shared_ptr<sdbusplus::asio::connection>& systemBus,
55     boost::asio::io_context& io,
56     const std::vector<std::filesystem::path>& configurationDirectories,
57     const std::filesystem::path& schemaDirectory) :
58     systemBus(systemBus),
59     objServer(sdbusplus::asio::object_server(systemBus, /*skipManager=*/true)),
60     configuration(configurationDirectories, schemaDirectory),
61     lastJson(nlohmann::json::object()),
62     systemConfiguration(nlohmann::json::object()), io(io),
63     dbus_interface(io, objServer, schemaDirectory), powerStatus(*systemBus),
64     propertiesChangedTimer(io)
65 {
66     // All other objects that EntityManager currently support are under the
67     // inventory subtree.
68     // See the discussion at
69     // https://discord.com/channels/775381525260664832/1018929092009144380
70     objServer.add_manager("/xyz/openbmc_project/inventory");
71 
72     entityIface = objServer.add_interface("/xyz/openbmc_project/EntityManager",
73                                           "xyz.openbmc_project.EntityManager");
74     entityIface->register_method("ReScan", [this]() {
75         propertiesChangedCallback();
76     });
77     dbus_interface::tryIfaceInitialize(entityIface);
78 
79     initFilters(configuration.probeInterfaces);
80 }
81 
postToDbus(const nlohmann::json & newConfiguration)82 void EntityManager::postToDbus(const nlohmann::json& newConfiguration)
83 {
84     std::map<std::string, std::string> newBoards; // path -> name
85 
86     // iterate through boards
87     for (const auto& [boardId, boardConfig] : newConfiguration.items())
88     {
89         const nlohmann::json::object_t* boardConfigPtr =
90             boardConfig.get_ptr<const nlohmann::json::object_t*>();
91         if (boardConfigPtr == nullptr)
92         {
93             lg2::error("boardConfig for {BOARD} was not an object", "BOARD",
94                        boardId);
95             continue;
96         }
97         postBoardToDBus(boardId, *boardConfigPtr, newBoards);
98     }
99 
100     for (const auto& [assocPath, assocPropValue] :
101          topology.getAssocs(std::views::keys(newBoards)))
102     {
103         auto findBoard = newBoards.find(assocPath);
104         if (findBoard == newBoards.end())
105         {
106             continue;
107         }
108 
109         auto ifacePtr = dbus_interface.createInterface(
110             assocPath,
111             sdbusplus::common::xyz::openbmc_project::association::Definitions::
112                 interface,
113             findBoard->second);
114 
115         ifacePtr->register_property("Associations", assocPropValue);
116         dbus_interface::tryIfaceInitialize(ifacePtr);
117     }
118 }
119 
postBoardToDBus(const std::string & boardId,const nlohmann::json::object_t & boardConfig,std::map<std::string,std::string> & newBoards)120 void EntityManager::postBoardToDBus(
121     const std::string& boardId, const nlohmann::json::object_t& boardConfig,
122     std::map<std::string, std::string>& newBoards)
123 {
124     auto boardNameIt = boardConfig.find("Name");
125     if (boardNameIt == boardConfig.end())
126     {
127         lg2::error("Unable to find name for {BOARD}", "BOARD", boardId);
128         return;
129     }
130     const std::string* boardNamePtr =
131         boardNameIt->second.get_ptr<const std::string*>();
132     if (boardNamePtr == nullptr)
133     {
134         lg2::error("Name for {BOARD} was not a string", "BOARD", boardId);
135         return;
136     }
137     std::string boardName = *boardNamePtr;
138     std::string boardNameOrig = *boardNamePtr;
139     std::string jsonPointerPath = "/" + boardId;
140     // loop through newConfiguration, but use values from system
141     // configuration to be able to modify via dbus later
142     auto boardValues = systemConfiguration[boardId];
143     auto findBoardType = boardValues.find("Type");
144     std::string boardType;
145     if (findBoardType != boardValues.end() &&
146         findBoardType->type() == nlohmann::json::value_t::string)
147     {
148         boardType = findBoardType->get<std::string>();
149         std::regex_replace(boardType.begin(), boardType.begin(),
150                            boardType.end(), illegalDbusMemberRegex, "_");
151     }
152     else
153     {
154         lg2::error("Unable to find type for {BOARD} reverting to Chassis.",
155                    "BOARD", boardName);
156         boardType = "Chassis";
157     }
158 
159     lg2::debug("post {TYPE} '{NAME}' to DBus", "TYPE", boardType, "NAME",
160                boardName);
161 
162     const std::string boardPath =
163         em_utils::buildInventorySystemPath(boardName, boardType);
164 
165     std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
166         dbus_interface.createInterface(
167             boardPath,
168             sdbusplus::common::xyz::openbmc_project::inventory::Item::interface,
169             boardName);
170 
171     const std::string invItemIntf = std::format(
172         "{}.{}",
173         sdbusplus::common::xyz::openbmc_project::inventory::Item::interface,
174         boardType);
175 
176     std::shared_ptr<sdbusplus::asio::dbus_interface> boardIface =
177         dbus_interface.createInterface(boardPath, invItemIntf, boardNameOrig);
178 
179     dbus_interface.createAddObjectMethod(jsonPointerPath, boardPath,
180                                          systemConfiguration, boardNameOrig);
181 
182     dbus_interface.populateInterfaceFromJson(
183         systemConfiguration, jsonPointerPath, boardIface, boardValues);
184     jsonPointerPath += "/";
185     // iterate through board properties
186     for (const auto& [propName, propValue] : boardValues.items())
187     {
188         if (propValue.type() == nlohmann::json::value_t::object)
189         {
190             std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
191                 dbus_interface.createInterface(boardPath, propName,
192                                                boardNameOrig);
193 
194             dbus_interface.populateInterfaceFromJson(
195                 systemConfiguration, jsonPointerPath + propName, iface,
196                 propValue);
197         }
198     }
199 
200     nlohmann::json::iterator exposes = boardValues.find("Exposes");
201     if (exposes == boardValues.end())
202     {
203         return;
204     }
205     // iterate through exposes
206     jsonPointerPath += "Exposes/";
207 
208     // store the board level pointer so we can modify it on the way down
209     std::string jsonPointerPathBoard = jsonPointerPath;
210     size_t exposesIndex = -1;
211     for (nlohmann::json& item : *exposes)
212     {
213         postExposesRecordsToDBus(item, exposesIndex, boardNameOrig,
214                                  jsonPointerPath, jsonPointerPathBoard,
215                                  boardPath, boardType);
216     }
217 
218     newBoards.emplace(boardPath, boardNameOrig);
219 }
220 
postExposesRecordsToDBus(nlohmann::json & item,size_t & exposesIndex,const std::string & boardNameOrig,std::string jsonPointerPath,const std::string & jsonPointerPathBoard,const std::string & boardPath,const std::string & boardType)221 void EntityManager::postExposesRecordsToDBus(
222     nlohmann::json& item, size_t& exposesIndex,
223     const std::string& boardNameOrig, std::string jsonPointerPath,
224     const std::string& jsonPointerPathBoard, const std::string& boardPath,
225     const std::string& boardType)
226 {
227     exposesIndex++;
228     jsonPointerPath = jsonPointerPathBoard;
229     jsonPointerPath += std::to_string(exposesIndex);
230 
231     auto findName = item.find("Name");
232     if (findName == item.end())
233     {
234         lg2::error("cannot find name in field {ITEM}", "ITEM", item);
235         return;
236     }
237     auto findStatus = item.find("Status");
238     // if status is not found it is assumed to be status = 'okay'
239     if (findStatus != item.end())
240     {
241         if (*findStatus == "disabled")
242         {
243             return;
244         }
245     }
246     auto findType = item.find("Type");
247     std::string itemType;
248     if (findType != item.end())
249     {
250         itemType = findType->get<std::string>();
251         std::regex_replace(itemType.begin(), itemType.begin(), itemType.end(),
252                            illegalDbusPathRegex, "_");
253     }
254     else
255     {
256         itemType = "unknown";
257     }
258     std::string itemName = findName->get<std::string>();
259     std::regex_replace(itemName.begin(), itemName.begin(), itemName.end(),
260                        illegalDbusMemberRegex, "_");
261     std::string ifacePath = boardPath;
262     ifacePath += "/";
263     ifacePath += itemName;
264 
265     if (itemType == "BMC")
266     {
267         std::shared_ptr<sdbusplus::asio::dbus_interface> bmcIface =
268             dbus_interface.createInterface(
269                 ifacePath,
270                 sdbusplus::common::xyz::openbmc_project::inventory::item::Bmc::
271                     interface,
272                 boardNameOrig);
273         dbus_interface.populateInterfaceFromJson(
274             systemConfiguration, jsonPointerPath, bmcIface, item,
275             getPermission(itemType));
276     }
277     else if (itemType == "System")
278     {
279         std::shared_ptr<sdbusplus::asio::dbus_interface> systemIface =
280             dbus_interface.createInterface(
281                 ifacePath,
282                 sdbusplus::common::xyz::openbmc_project::inventory::item::
283                     System::interface,
284                 boardNameOrig);
285         dbus_interface.populateInterfaceFromJson(
286             systemConfiguration, jsonPointerPath, systemIface, item,
287             getPermission(itemType));
288     }
289 
290     for (const auto& [name, config] : item.items())
291     {
292         jsonPointerPath = jsonPointerPathBoard;
293         jsonPointerPath.append(std::to_string(exposesIndex))
294             .append("/")
295             .append(name);
296 
297         if (!postConfigurationRecord(name, config, boardNameOrig, itemType,
298                                      jsonPointerPath, ifacePath))
299         {
300             break;
301         }
302     }
303 
304     std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface =
305         dbus_interface.createInterface(
306             ifacePath, "xyz.openbmc_project.Configuration." + itemType,
307             boardNameOrig);
308 
309     dbus_interface.populateInterfaceFromJson(
310         systemConfiguration, jsonPointerPath, itemIface, item,
311         getPermission(itemType));
312 
313     topology.addBoard(boardPath, boardType, boardNameOrig, item);
314 }
315 
postConfigurationRecord(const std::string & name,nlohmann::json & config,const std::string & boardNameOrig,const std::string & itemType,const std::string & jsonPointerPath,const std::string & ifacePath)316 bool EntityManager::postConfigurationRecord(
317     const std::string& name, nlohmann::json& config,
318     const std::string& boardNameOrig, const std::string& itemType,
319     const std::string& jsonPointerPath, const std::string& ifacePath)
320 {
321     if (config.type() == nlohmann::json::value_t::object)
322     {
323         std::string ifaceName = "xyz.openbmc_project.Configuration.";
324         ifaceName.append(itemType).append(".").append(name);
325 
326         std::shared_ptr<sdbusplus::asio::dbus_interface> objectIface =
327             dbus_interface.createInterface(ifacePath, ifaceName, boardNameOrig);
328 
329         dbus_interface.populateInterfaceFromJson(
330             systemConfiguration, jsonPointerPath, objectIface, config,
331             getPermission(name));
332     }
333     else if (config.type() == nlohmann::json::value_t::array)
334     {
335         size_t index = 0;
336         if (config.empty())
337         {
338             return true;
339         }
340         bool isLegal = true;
341         auto type = config[0].type();
342         if (type != nlohmann::json::value_t::object)
343         {
344             return true;
345         }
346 
347         // verify legal json
348         for (const auto& arrayItem : config)
349         {
350             if (arrayItem.type() != type)
351             {
352                 isLegal = false;
353                 break;
354             }
355         }
356         if (!isLegal)
357         {
358             lg2::error("dbus format error {JSON}", "JSON", config);
359             return false;
360         }
361 
362         for (auto& arrayItem : config)
363         {
364             std::string ifaceName = "xyz.openbmc_project.Configuration.";
365             ifaceName.append(itemType).append(".").append(name);
366             ifaceName.append(std::to_string(index));
367 
368             std::shared_ptr<sdbusplus::asio::dbus_interface> objectIface =
369                 dbus_interface.createInterface(ifacePath, ifaceName,
370                                                boardNameOrig);
371 
372             dbus_interface.populateInterfaceFromJson(
373                 systemConfiguration,
374                 jsonPointerPath + "/" + std::to_string(index), objectIface,
375                 arrayItem, getPermission(name));
376             index++;
377         }
378     }
379 
380     return true;
381 }
382 
deviceRequiresPowerOn(const nlohmann::json & entity)383 static bool deviceRequiresPowerOn(const nlohmann::json& entity)
384 {
385     auto powerState = entity.find("PowerState");
386     if (powerState == entity.end())
387     {
388         return false;
389     }
390 
391     const auto* ptr = powerState->get_ptr<const std::string*>();
392     if (ptr == nullptr)
393     {
394         return false;
395     }
396 
397     return *ptr == "On" || *ptr == "BiosPost";
398 }
399 
pruneDevice(const nlohmann::json & systemConfiguration,const bool powerOff,const bool scannedPowerOff,const std::string & name,const nlohmann::json & device)400 static void pruneDevice(const nlohmann::json& systemConfiguration,
401                         const bool powerOff, const bool scannedPowerOff,
402                         const std::string& name, const nlohmann::json& device)
403 {
404     if (systemConfiguration.contains(name))
405     {
406         return;
407     }
408 
409     if (deviceRequiresPowerOn(device) && (powerOff || scannedPowerOff))
410     {
411         return;
412     }
413 
414     logDeviceRemoved(device);
415 }
416 
startRemovedTimer(boost::asio::steady_timer & timer)417 void EntityManager::startRemovedTimer(boost::asio::steady_timer& timer)
418 {
419     if (systemConfiguration.empty() || lastJson.empty())
420     {
421         return; // not ready yet
422     }
423     if (scannedPowerOn)
424     {
425         return;
426     }
427 
428     if (!powerStatus.isPowerOn() && scannedPowerOff)
429     {
430         return;
431     }
432 
433     timer.expires_after(std::chrono::seconds(10));
434     timer.async_wait([this](const boost::system::error_code& ec) {
435         if (ec == boost::asio::error::operation_aborted)
436         {
437             return;
438         }
439 
440         bool powerOff = !powerStatus.isPowerOn();
441         for (const auto& [name, device] : lastJson.items())
442         {
443             pruneDevice(systemConfiguration, powerOff, scannedPowerOff, name,
444                         device);
445         }
446 
447         scannedPowerOff = true;
448         if (!powerOff)
449         {
450             scannedPowerOn = true;
451         }
452     });
453 }
454 
pruneConfiguration(bool powerOff,const std::string & name,const nlohmann::json & device)455 void EntityManager::pruneConfiguration(bool powerOff, const std::string& name,
456                                        const nlohmann::json& device)
457 {
458     lg2::debug("pruning configuration");
459 
460     if (powerOff && deviceRequiresPowerOn(device))
461     {
462         // power not on yet, don't know if it's there or not
463         return;
464     }
465 
466     auto& ifaces = dbus_interface.getDeviceInterfaces(device);
467     for (auto& iface : ifaces)
468     {
469         auto sharedPtr = iface.lock();
470         if (!!sharedPtr)
471         {
472             objServer.remove_interface(sharedPtr);
473         }
474     }
475 
476     ifaces.clear();
477     systemConfiguration.erase(name);
478     topology.remove(device["Name"].get<std::string>());
479     logDeviceRemoved(device);
480 }
481 
publishNewConfiguration(const size_t & instance,const size_t count,boost::asio::steady_timer & timer,const nlohmann::json newConfiguration)482 void EntityManager::publishNewConfiguration(
483     const size_t& instance, const size_t count,
484     boost::asio::steady_timer& timer, // Gerrit discussion:
485     // https://gerrit.openbmc-project.xyz/c/openbmc/entity-manager/+/52316/6
486     //
487     // Discord discussion:
488     // https://discord.com/channels/775381525260664832/867820390406422538/958048437729910854
489     //
490     // NOLINTNEXTLINE(performance-unnecessary-value-param)
491     const nlohmann::json newConfiguration)
492 {
493     loadOverlays(newConfiguration, io);
494 
495     boost::asio::post(io, [this]() {
496         if (!writeJsonFiles(systemConfiguration))
497         {
498             lg2::error("Error writing json files");
499         }
500     });
501 
502     boost::asio::post(io, [this, &instance, count, &timer, newConfiguration]() {
503         postToDbus(newConfiguration);
504         if (count == instance)
505         {
506             startRemovedTimer(timer);
507         }
508     });
509 }
510 
511 // main properties changed entry
propertiesChangedCallback()512 void EntityManager::propertiesChangedCallback()
513 {
514     lg2::debug("properties changed callback");
515     propertiesChangedInstance++;
516     size_t count = propertiesChangedInstance;
517 
518     propertiesChangedTimer.expires_after(std::chrono::milliseconds(500));
519 
520     // setup an async wait as we normally get flooded with new requests
521     propertiesChangedTimer.async_wait(
522         [this, count](const boost::system::error_code& ec) {
523             lg2::debug("properties changed callback timer expired");
524             if (ec == boost::asio::error::operation_aborted)
525             {
526                 // we were cancelled
527                 return;
528             }
529             if (ec)
530             {
531                 lg2::error("async wait error {ERR}", "ERR", ec.message());
532                 return;
533             }
534 
535             if (propertiesChangedInProgress)
536             {
537                 propertiesChangedCallback();
538                 return;
539             }
540             propertiesChangedInProgress = true;
541 
542             lg2::debug("properties changed callback in progress");
543 
544             nlohmann::json oldConfiguration = systemConfiguration;
545             auto missingConfigurations = std::make_shared<nlohmann::json>();
546             *missingConfigurations = systemConfiguration;
547 
548             auto perfScan = std::make_shared<scan::PerformScan>(
549                 *this, *missingConfigurations, configuration.configurations, io,
550                 [this, count, oldConfiguration, missingConfigurations]() {
551                     // this is something that since ac has been applied to the
552                     // bmc we saw, and we no longer see it
553                     bool powerOff = !powerStatus.isPowerOn();
554                     for (const auto& [name, device] :
555                          missingConfigurations->items())
556                     {
557                         pruneConfiguration(powerOff, name, device);
558                     }
559                     nlohmann::json newConfiguration = systemConfiguration;
560 
561                     deriveNewConfiguration(oldConfiguration, newConfiguration);
562 
563                     for (const auto& [_, device] : newConfiguration.items())
564                     {
565                         logDeviceAdded(device);
566                     }
567 
568                     propertiesChangedInProgress = false;
569 
570                     boost::asio::post(io, [this, newConfiguration, count] {
571                         publishNewConfiguration(
572                             std::ref(propertiesChangedInstance), count,
573                             std::ref(propertiesChangedTimer), newConfiguration);
574                     });
575                 });
576             perfScan->run();
577         });
578 }
579 
580 // Check if InterfacesAdded payload contains an iface that needs probing.
iaContainsProbeInterface(sdbusplus::message_t & msg,const std::unordered_set<std::string> & probeInterfaces)581 static bool iaContainsProbeInterface(
582     sdbusplus::message_t& msg,
583     const std::unordered_set<std::string>& probeInterfaces)
584 {
585     sdbusplus::message::object_path path;
586     DBusObject interfaces;
587     msg.read(path, interfaces);
588     return std::ranges::any_of(interfaces | std::views::keys,
589                                [&probeInterfaces](const auto& ifaceName) {
590                                    return probeInterfaces.contains(ifaceName);
591                                });
592 }
593 
594 // Check if InterfacesRemoved payload contains an iface that needs probing.
irContainsProbeInterface(const std::vector<std::string> & interfaces,const std::unordered_set<std::string> & probeInterfaces)595 static bool irContainsProbeInterface(
596     const std::vector<std::string>& interfaces,
597     const std::unordered_set<std::string>& probeInterfaces)
598 {
599     return std::ranges::any_of(interfaces,
600                                [&probeInterfaces](const auto& ifaceName) {
601                                    return probeInterfaces.contains(ifaceName);
602                                });
603 }
604 
handleCurrentConfigurationJson()605 void EntityManager::handleCurrentConfigurationJson()
606 {
607     if (EM_CACHE_CONFIGURATION && em_utils::fwVersionIsSame())
608     {
609         if (std::filesystem::is_regular_file(currentConfiguration))
610         {
611             // this file could just be deleted, but it's nice for debug
612             std::filesystem::create_directory(tempConfigDir);
613             std::filesystem::remove(lastConfiguration);
614             std::filesystem::copy(currentConfiguration, lastConfiguration);
615             std::filesystem::remove(currentConfiguration);
616 
617             std::ifstream jsonStream(lastConfiguration);
618             if (jsonStream.good())
619             {
620                 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
621                 if (data.is_discarded())
622                 {
623                     lg2::error("syntax error in {PATH}", "PATH",
624                                lastConfiguration);
625                 }
626                 else
627                 {
628                     lastJson = std::move(data);
629                 }
630             }
631             else
632             {
633                 lg2::error("unable to open {PATH}", "PATH", lastConfiguration);
634             }
635         }
636     }
637     else
638     {
639         // not an error, just logging at this level to make it in the journal
640         std::error_code ec;
641         lg2::error("Clearing previous configuration");
642         std::filesystem::remove(currentConfiguration, ec);
643     }
644 }
645 
registerCallback(const std::string & path)646 void EntityManager::registerCallback(const std::string& path)
647 {
648     if (dbusMatches.contains(path))
649     {
650         return;
651     }
652 
653     lg2::debug("creating PropertiesChanged match on {PATH}", "PATH", path);
654 
655     std::function<void(sdbusplus::message_t & message)> eventHandler =
656         [&](sdbusplus::message_t&) { propertiesChangedCallback(); };
657 
658     sdbusplus::bus::match_t match(
659         static_cast<sdbusplus::bus_t&>(*systemBus),
660         "type='signal',member='PropertiesChanged',path='" + path + "'",
661         eventHandler);
662     dbusMatches.emplace(path, std::move(match));
663 }
664 
665 // We need a poke from DBus for static providers that create all their
666 // objects prior to claiming a well-known name, and thus don't emit any
667 // org.freedesktop.DBus.Properties signals.  Similarly if a process exits
668 // for any reason, expected or otherwise, we'll need a poke to remove
669 // entities from DBus.
initFilters(const std::unordered_set<std::string> & probeInterfaces)670 void EntityManager::initFilters(
671     const std::unordered_set<std::string>& probeInterfaces)
672 {
673     nameOwnerChangedMatch = std::make_unique<sdbusplus::bus::match_t>(
674         static_cast<sdbusplus::bus_t&>(*systemBus),
675         sdbusplus::bus::match::rules::nameOwnerChanged(),
676         [this](sdbusplus::message_t& m) {
677             auto [name, oldOwner,
678                   newOwner] = m.unpack<std::string, std::string, std::string>();
679 
680             if (name.starts_with(':'))
681             {
682                 // We should do nothing with unique-name connections.
683                 return;
684             }
685 
686             propertiesChangedCallback();
687         });
688 
689     // We also need a poke from DBus when new interfaces are created or
690     // destroyed.
691     interfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
692         static_cast<sdbusplus::bus_t&>(*systemBus),
693         sdbusplus::bus::match::rules::interfacesAdded(),
694         [this, probeInterfaces](sdbusplus::message_t& msg) {
695             if (iaContainsProbeInterface(msg, probeInterfaces))
696             {
697                 propertiesChangedCallback();
698             }
699         });
700 
701     interfacesRemovedMatch = std::make_unique<sdbusplus::bus::match_t>(
702         static_cast<sdbusplus::bus_t&>(*systemBus),
703         sdbusplus::bus::match::rules::interfacesRemoved(),
704         [this, probeInterfaces](sdbusplus::message_t& msg) {
705             auto [path, interfaces] =
706                 msg.unpack<sdbusplus::message::object_path,
707                            std::vector<std::string>>();
708 
709             if (irContainsProbeInterface(interfaces, probeInterfaces))
710             {
711                 // Clean up match on probe interface removal to avoid leaks
712                 dbusMatches.erase(path.str);
713                 propertiesChangedCallback();
714             }
715         });
716 }
717