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