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 const std::string boardPath =
160 em_utils::buildInventorySystemPath(boardName, boardType);
161
162 std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
163 dbus_interface.createInterface(
164 boardPath,
165 sdbusplus::common::xyz::openbmc_project::inventory::Item::interface,
166 boardName);
167
168 const std::string invItemIntf = std::format(
169 "{}.{}",
170 sdbusplus::common::xyz::openbmc_project::inventory::Item::interface,
171 boardType);
172
173 std::shared_ptr<sdbusplus::asio::dbus_interface> boardIface =
174 dbus_interface.createInterface(boardPath, invItemIntf, boardNameOrig);
175
176 dbus_interface.createAddObjectMethod(jsonPointerPath, boardPath,
177 systemConfiguration, boardNameOrig);
178
179 dbus_interface.populateInterfaceFromJson(
180 systemConfiguration, jsonPointerPath, boardIface, boardValues);
181 jsonPointerPath += "/";
182 // iterate through board properties
183 for (const auto& [propName, propValue] : boardValues.items())
184 {
185 if (propValue.type() == nlohmann::json::value_t::object)
186 {
187 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
188 dbus_interface.createInterface(boardPath, propName,
189 boardNameOrig);
190
191 dbus_interface.populateInterfaceFromJson(
192 systemConfiguration, jsonPointerPath + propName, iface,
193 propValue);
194 }
195 }
196
197 nlohmann::json::iterator exposes = boardValues.find("Exposes");
198 if (exposes == boardValues.end())
199 {
200 return;
201 }
202 // iterate through exposes
203 jsonPointerPath += "Exposes/";
204
205 // store the board level pointer so we can modify it on the way down
206 std::string jsonPointerPathBoard = jsonPointerPath;
207 size_t exposesIndex = -1;
208 for (nlohmann::json& item : *exposes)
209 {
210 postExposesRecordsToDBus(item, exposesIndex, boardNameOrig,
211 jsonPointerPath, jsonPointerPathBoard,
212 boardPath, boardType);
213 }
214
215 newBoards.emplace(boardPath, boardNameOrig);
216 }
217
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)218 void EntityManager::postExposesRecordsToDBus(
219 nlohmann::json& item, size_t& exposesIndex,
220 const std::string& boardNameOrig, std::string jsonPointerPath,
221 const std::string& jsonPointerPathBoard, const std::string& boardPath,
222 const std::string& boardType)
223 {
224 exposesIndex++;
225 jsonPointerPath = jsonPointerPathBoard;
226 jsonPointerPath += std::to_string(exposesIndex);
227
228 auto findName = item.find("Name");
229 if (findName == item.end())
230 {
231 lg2::error("cannot find name in field {ITEM}", "ITEM", item);
232 return;
233 }
234 auto findStatus = item.find("Status");
235 // if status is not found it is assumed to be status = 'okay'
236 if (findStatus != item.end())
237 {
238 if (*findStatus == "disabled")
239 {
240 return;
241 }
242 }
243 auto findType = item.find("Type");
244 std::string itemType;
245 if (findType != item.end())
246 {
247 itemType = findType->get<std::string>();
248 std::regex_replace(itemType.begin(), itemType.begin(), itemType.end(),
249 illegalDbusPathRegex, "_");
250 }
251 else
252 {
253 itemType = "unknown";
254 }
255 std::string itemName = findName->get<std::string>();
256 std::regex_replace(itemName.begin(), itemName.begin(), itemName.end(),
257 illegalDbusMemberRegex, "_");
258 std::string ifacePath = boardPath;
259 ifacePath += "/";
260 ifacePath += itemName;
261
262 if (itemType == "BMC")
263 {
264 std::shared_ptr<sdbusplus::asio::dbus_interface> bmcIface =
265 dbus_interface.createInterface(
266 ifacePath,
267 sdbusplus::common::xyz::openbmc_project::inventory::item::Bmc::
268 interface,
269 boardNameOrig);
270 dbus_interface.populateInterfaceFromJson(
271 systemConfiguration, jsonPointerPath, bmcIface, item,
272 getPermission(itemType));
273 }
274 else if (itemType == "System")
275 {
276 std::shared_ptr<sdbusplus::asio::dbus_interface> systemIface =
277 dbus_interface.createInterface(
278 ifacePath,
279 sdbusplus::common::xyz::openbmc_project::inventory::item::
280 System::interface,
281 boardNameOrig);
282 dbus_interface.populateInterfaceFromJson(
283 systemConfiguration, jsonPointerPath, systemIface, item,
284 getPermission(itemType));
285 }
286
287 for (const auto& [name, config] : item.items())
288 {
289 jsonPointerPath = jsonPointerPathBoard;
290 jsonPointerPath.append(std::to_string(exposesIndex))
291 .append("/")
292 .append(name);
293
294 if (!postConfigurationRecord(name, config, boardNameOrig, itemType,
295 jsonPointerPath, ifacePath))
296 {
297 break;
298 }
299 }
300
301 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface =
302 dbus_interface.createInterface(
303 ifacePath, "xyz.openbmc_project.Configuration." + itemType,
304 boardNameOrig);
305
306 dbus_interface.populateInterfaceFromJson(
307 systemConfiguration, jsonPointerPath, itemIface, item,
308 getPermission(itemType));
309
310 topology.addBoard(boardPath, boardType, boardNameOrig, item);
311 }
312
postConfigurationRecord(const std::string & name,nlohmann::json & config,const std::string & boardNameOrig,const std::string & itemType,const std::string & jsonPointerPath,const std::string & ifacePath)313 bool EntityManager::postConfigurationRecord(
314 const std::string& name, nlohmann::json& config,
315 const std::string& boardNameOrig, const std::string& itemType,
316 const std::string& jsonPointerPath, const std::string& ifacePath)
317 {
318 if (config.type() == nlohmann::json::value_t::object)
319 {
320 std::string ifaceName = "xyz.openbmc_project.Configuration.";
321 ifaceName.append(itemType).append(".").append(name);
322
323 std::shared_ptr<sdbusplus::asio::dbus_interface> objectIface =
324 dbus_interface.createInterface(ifacePath, ifaceName, boardNameOrig);
325
326 dbus_interface.populateInterfaceFromJson(
327 systemConfiguration, jsonPointerPath, objectIface, config,
328 getPermission(name));
329 }
330 else if (config.type() == nlohmann::json::value_t::array)
331 {
332 size_t index = 0;
333 if (config.empty())
334 {
335 return true;
336 }
337 bool isLegal = true;
338 auto type = config[0].type();
339 if (type != nlohmann::json::value_t::object)
340 {
341 return true;
342 }
343
344 // verify legal json
345 for (const auto& arrayItem : config)
346 {
347 if (arrayItem.type() != type)
348 {
349 isLegal = false;
350 break;
351 }
352 }
353 if (!isLegal)
354 {
355 lg2::error("dbus format error {JSON}", "JSON", config);
356 return false;
357 }
358
359 for (auto& arrayItem : config)
360 {
361 std::string ifaceName = "xyz.openbmc_project.Configuration.";
362 ifaceName.append(itemType).append(".").append(name);
363 ifaceName.append(std::to_string(index));
364
365 std::shared_ptr<sdbusplus::asio::dbus_interface> objectIface =
366 dbus_interface.createInterface(ifacePath, ifaceName,
367 boardNameOrig);
368
369 dbus_interface.populateInterfaceFromJson(
370 systemConfiguration,
371 jsonPointerPath + "/" + std::to_string(index), objectIface,
372 arrayItem, getPermission(name));
373 index++;
374 }
375 }
376
377 return true;
378 }
379
deviceRequiresPowerOn(const nlohmann::json & entity)380 static bool deviceRequiresPowerOn(const nlohmann::json& entity)
381 {
382 auto powerState = entity.find("PowerState");
383 if (powerState == entity.end())
384 {
385 return false;
386 }
387
388 const auto* ptr = powerState->get_ptr<const std::string*>();
389 if (ptr == nullptr)
390 {
391 return false;
392 }
393
394 return *ptr == "On" || *ptr == "BiosPost";
395 }
396
pruneDevice(const nlohmann::json & systemConfiguration,const bool powerOff,const bool scannedPowerOff,const std::string & name,const nlohmann::json & device)397 static void pruneDevice(const nlohmann::json& systemConfiguration,
398 const bool powerOff, const bool scannedPowerOff,
399 const std::string& name, const nlohmann::json& device)
400 {
401 if (systemConfiguration.contains(name))
402 {
403 return;
404 }
405
406 if (deviceRequiresPowerOn(device) && (powerOff || scannedPowerOff))
407 {
408 return;
409 }
410
411 logDeviceRemoved(device);
412 }
413
startRemovedTimer(boost::asio::steady_timer & timer,nlohmann::json & systemConfiguration)414 void EntityManager::startRemovedTimer(boost::asio::steady_timer& timer,
415 nlohmann::json& systemConfiguration)
416 {
417 if (systemConfiguration.empty() || lastJson.empty())
418 {
419 return; // not ready yet
420 }
421 if (scannedPowerOn)
422 {
423 return;
424 }
425
426 if (!powerStatus.isPowerOn() && scannedPowerOff)
427 {
428 return;
429 }
430
431 timer.expires_after(std::chrono::seconds(10));
432 timer.async_wait(
433 [&systemConfiguration, this](const boost::system::error_code& ec) {
434 if (ec == boost::asio::error::operation_aborted)
435 {
436 return;
437 }
438
439 bool powerOff = !powerStatus.isPowerOn();
440 for (const auto& [name, device] : lastJson.items())
441 {
442 pruneDevice(systemConfiguration, powerOff, scannedPowerOff,
443 name, device);
444 }
445
446 scannedPowerOff = true;
447 if (!powerOff)
448 {
449 scannedPowerOn = true;
450 }
451 });
452 }
453
pruneConfiguration(bool powerOff,const std::string & name,const nlohmann::json & device)454 void EntityManager::pruneConfiguration(bool powerOff, const std::string& name,
455 const nlohmann::json& device)
456 {
457 if (powerOff && deviceRequiresPowerOn(device))
458 {
459 // power not on yet, don't know if it's there or not
460 return;
461 }
462
463 auto& ifaces = dbus_interface.getDeviceInterfaces(device);
464 for (auto& iface : ifaces)
465 {
466 auto sharedPtr = iface.lock();
467 if (!!sharedPtr)
468 {
469 objServer.remove_interface(sharedPtr);
470 }
471 }
472
473 ifaces.clear();
474 systemConfiguration.erase(name);
475 topology.remove(device["Name"].get<std::string>());
476 logDeviceRemoved(device);
477 }
478
publishNewConfiguration(const size_t & instance,const size_t count,boost::asio::steady_timer & timer,const nlohmann::json newConfiguration)479 void EntityManager::publishNewConfiguration(
480 const size_t& instance, const size_t count,
481 boost::asio::steady_timer& timer, // Gerrit discussion:
482 // https://gerrit.openbmc-project.xyz/c/openbmc/entity-manager/+/52316/6
483 //
484 // Discord discussion:
485 // https://discord.com/channels/775381525260664832/867820390406422538/958048437729910854
486 //
487 // NOLINTNEXTLINE(performance-unnecessary-value-param)
488 const nlohmann::json newConfiguration)
489 {
490 loadOverlays(newConfiguration, io);
491
492 boost::asio::post(io, [this]() {
493 if (!writeJsonFiles(systemConfiguration))
494 {
495 lg2::error("Error writing json files");
496 }
497 });
498
499 boost::asio::post(io, [this, &instance, count, &timer, newConfiguration]() {
500 postToDbus(newConfiguration);
501 if (count == instance)
502 {
503 startRemovedTimer(timer, systemConfiguration);
504 }
505 });
506 }
507
508 // main properties changed entry
propertiesChangedCallback()509 void EntityManager::propertiesChangedCallback()
510 {
511 propertiesChangedInstance++;
512 size_t count = propertiesChangedInstance;
513
514 propertiesChangedTimer.expires_after(std::chrono::milliseconds(500));
515
516 // setup an async wait as we normally get flooded with new requests
517 propertiesChangedTimer.async_wait(
518 [this, count](const boost::system::error_code& ec) {
519 if (ec == boost::asio::error::operation_aborted)
520 {
521 // we were cancelled
522 return;
523 }
524 if (ec)
525 {
526 lg2::error("async wait error {ERR}", "ERR", ec.message());
527 return;
528 }
529
530 if (propertiesChangedInProgress)
531 {
532 propertiesChangedCallback();
533 return;
534 }
535 propertiesChangedInProgress = true;
536
537 nlohmann::json oldConfiguration = systemConfiguration;
538 auto missingConfigurations = std::make_shared<nlohmann::json>();
539 *missingConfigurations = systemConfiguration;
540
541 auto perfScan = std::make_shared<scan::PerformScan>(
542 *this, *missingConfigurations, configuration.configurations, io,
543 [this, count, oldConfiguration, missingConfigurations]() {
544 // this is something that since ac has been applied to the
545 // bmc we saw, and we no longer see it
546 bool powerOff = !powerStatus.isPowerOn();
547 for (const auto& [name, device] :
548 missingConfigurations->items())
549 {
550 pruneConfiguration(powerOff, name, device);
551 }
552 nlohmann::json newConfiguration = systemConfiguration;
553
554 deriveNewConfiguration(oldConfiguration, newConfiguration);
555
556 for (const auto& [_, device] : newConfiguration.items())
557 {
558 logDeviceAdded(device);
559 }
560
561 propertiesChangedInProgress = false;
562
563 boost::asio::post(io, [this, newConfiguration, count] {
564 publishNewConfiguration(
565 std::ref(propertiesChangedInstance), count,
566 std::ref(propertiesChangedTimer), newConfiguration);
567 });
568 });
569 perfScan->run();
570 });
571 }
572
573 // Check if InterfacesAdded payload contains an iface that needs probing.
iaContainsProbeInterface(sdbusplus::message_t & msg,const std::unordered_set<std::string> & probeInterfaces)574 static bool iaContainsProbeInterface(
575 sdbusplus::message_t& msg,
576 const std::unordered_set<std::string>& probeInterfaces)
577 {
578 sdbusplus::message::object_path path;
579 DBusObject interfaces;
580 msg.read(path, interfaces);
581 return std::ranges::any_of(interfaces | std::views::keys,
582 [&probeInterfaces](const auto& ifaceName) {
583 return probeInterfaces.contains(ifaceName);
584 });
585 }
586
587 // Check if InterfacesRemoved payload contains an iface that needs probing.
irContainsProbeInterface(sdbusplus::message_t & msg,const std::unordered_set<std::string> & probeInterfaces)588 static bool irContainsProbeInterface(
589 sdbusplus::message_t& msg,
590 const std::unordered_set<std::string>& probeInterfaces)
591 {
592 sdbusplus::message::object_path path;
593 std::vector<std::string> interfaces;
594 msg.read(path, interfaces);
595 return std::ranges::any_of(interfaces,
596 [&probeInterfaces](const auto& ifaceName) {
597 return probeInterfaces.contains(ifaceName);
598 });
599 }
600
handleCurrentConfigurationJson()601 void EntityManager::handleCurrentConfigurationJson()
602 {
603 if (EM_CACHE_CONFIGURATION && em_utils::fwVersionIsSame())
604 {
605 if (std::filesystem::is_regular_file(currentConfiguration))
606 {
607 // this file could just be deleted, but it's nice for debug
608 std::filesystem::create_directory(tempConfigDir);
609 std::filesystem::remove(lastConfiguration);
610 std::filesystem::copy(currentConfiguration, lastConfiguration);
611 std::filesystem::remove(currentConfiguration);
612
613 std::ifstream jsonStream(lastConfiguration);
614 if (jsonStream.good())
615 {
616 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
617 if (data.is_discarded())
618 {
619 lg2::error("syntax error in {PATH}", "PATH",
620 lastConfiguration);
621 }
622 else
623 {
624 lastJson = std::move(data);
625 }
626 }
627 else
628 {
629 lg2::error("unable to open {PATH}", "PATH", lastConfiguration);
630 }
631 }
632 }
633 else
634 {
635 // not an error, just logging at this level to make it in the journal
636 std::error_code ec;
637 lg2::error("Clearing previous configuration");
638 std::filesystem::remove(currentConfiguration, ec);
639 }
640 }
641
registerCallback(const std::string & path)642 void EntityManager::registerCallback(const std::string& path)
643 {
644 if (dbusMatches.contains(path))
645 {
646 return;
647 }
648
649 lg2::debug("creating PropertiesChanged match on {PATH}", "PATH", path);
650
651 std::function<void(sdbusplus::message_t & message)> eventHandler =
652 [&](sdbusplus::message_t&) { propertiesChangedCallback(); };
653
654 sdbusplus::bus::match_t match(
655 static_cast<sdbusplus::bus_t&>(*systemBus),
656 "type='signal',member='PropertiesChanged',path='" + path + "'",
657 eventHandler);
658 dbusMatches.emplace(path, std::move(match));
659 }
660
661 // We need a poke from DBus for static providers that create all their
662 // objects prior to claiming a well-known name, and thus don't emit any
663 // org.freedesktop.DBus.Properties signals. Similarly if a process exits
664 // for any reason, expected or otherwise, we'll need a poke to remove
665 // entities from DBus.
initFilters(const std::unordered_set<std::string> & probeInterfaces)666 void EntityManager::initFilters(
667 const std::unordered_set<std::string>& probeInterfaces)
668 {
669 nameOwnerChangedMatch = std::make_unique<sdbusplus::bus::match_t>(
670 static_cast<sdbusplus::bus_t&>(*systemBus),
671 sdbusplus::bus::match::rules::nameOwnerChanged(),
672 [this](sdbusplus::message_t& m) {
673 auto [name, oldOwner,
674 newOwner] = m.unpack<std::string, std::string, std::string>();
675
676 if (name.starts_with(':'))
677 {
678 // We should do nothing with unique-name connections.
679 return;
680 }
681
682 propertiesChangedCallback();
683 });
684
685 // We also need a poke from DBus when new interfaces are created or
686 // destroyed.
687 interfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
688 static_cast<sdbusplus::bus_t&>(*systemBus),
689 sdbusplus::bus::match::rules::interfacesAdded(),
690 [this, probeInterfaces](sdbusplus::message_t& msg) {
691 if (iaContainsProbeInterface(msg, probeInterfaces))
692 {
693 propertiesChangedCallback();
694 }
695 });
696
697 interfacesRemovedMatch = std::make_unique<sdbusplus::bus::match_t>(
698 static_cast<sdbusplus::bus_t&>(*systemBus),
699 sdbusplus::bus::match::rules::interfacesRemoved(),
700 [this, probeInterfaces](sdbusplus::message_t& msg) {
701 if (irContainsProbeInterface(msg, probeInterfaces))
702 {
703 propertiesChangedCallback();
704 }
705 });
706 }
707