blob: 4623141a916fa6e8918d40de366e000abe632c91 [file] [log] [blame]
Alexander Hansen4e1142d2025-07-25 17:07:27 +02001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
James Feist3cb5fec2018-01-23 14:41:51 -08003
Brad Bishope45d8c72022-05-25 15:12:53 -04004#include "entity_manager.hpp"
James Feist1df06a42019-04-11 14:23:04 -07005
Christopher Meisfc9e7fd2025-04-03 13:13:35 +02006#include "../utils.hpp"
7#include "../variant_visitors.hpp"
Christopher Meisbdaa6b22025-04-02 10:49:02 +02008#include "configuration.hpp"
Christopher Meis12bea9b2025-04-03 10:14:42 +02009#include "dbus_interface.hpp"
Alexander Hansenf57a2592025-06-27 15:07:07 +020010#include "log_device_inventory.hpp"
Brad Bishope45d8c72022-05-25 15:12:53 -040011#include "overlay.hpp"
Christopher Meis26fbbd52025-03-26 14:55:06 +010012#include "perform_scan.hpp"
Alexander Hansen60803182025-06-27 14:41:28 +020013#include "phosphor-logging/lg2.hpp"
Benjamin Fairca2eb042022-09-13 06:40:42 +000014#include "topology.hpp"
Christopher Meis59ef1e72025-04-16 08:53:25 +020015#include "utils.hpp"
James Feist481c5d52019-08-13 14:40:40 -070016
James Feist11be6672018-04-06 14:05:32 -070017#include <boost/algorithm/string/case_conv.hpp>
James Feistf5125b02019-06-06 11:27:43 -070018#include <boost/algorithm/string/classification.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080019#include <boost/algorithm/string/replace.hpp>
James Feistf5125b02019-06-06 11:27:43 -070020#include <boost/algorithm/string/split.hpp>
James Feist02d2b932020-02-06 16:28:48 -080021#include <boost/asio/io_context.hpp>
Ed Tanous49a888c2023-03-06 13:44:51 -080022#include <boost/asio/post.hpp>
James Feistb1728ca2020-04-30 15:40:55 -070023#include <boost/asio/steady_timer.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080024#include <boost/container/flat_map.hpp>
25#include <boost/container/flat_set.hpp>
James Feistf5125b02019-06-06 11:27:43 -070026#include <boost/range/iterator_range.hpp>
James Feist8c505da2020-05-28 10:06:33 -070027#include <nlohmann/json.hpp>
28#include <sdbusplus/asio/connection.hpp>
29#include <sdbusplus/asio/object_server.hpp>
30
James Feist637b3ef2019-04-15 16:35:30 -070031#include <filesystem>
James Feista465ccc2019-02-08 12:51:01 -080032#include <fstream>
Andrew Jefferye35d0ac2022-03-24 15:53:13 +103033#include <functional>
James Feista465ccc2019-02-08 12:51:01 -080034#include <iostream>
Andrew Jeffery666583b2021-12-01 15:50:12 +103035#include <map>
James Feista465ccc2019-02-08 12:51:01 -080036#include <regex>
James Feist1df06a42019-04-11 14:23:04 -070037constexpr const char* tempConfigDir = "/tmp/configuration/";
38constexpr const char* lastConfiguration = "/tmp/configuration/last.json";
James Feistf1b14142019-04-10 15:22:09 -070039
Adrian Ambrożewiczc789fca2020-05-14 15:50:05 +020040static constexpr std::array<const char*, 6> settableInterfaces = {
41 "FanProfile", "Pid", "Pid.Zone", "Stepwise", "Thresholds", "Polling"};
James Feist3cb5fec2018-01-23 14:41:51 -080042
Ed Tanous07d467b2021-02-23 14:48:37 -080043const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]");
44const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]");
James Feist1b2e2242018-01-30 13:45:19 -080045
James Feista465ccc2019-02-08 12:51:01 -080046sdbusplus::asio::PropertyPermission getPermission(const std::string& interface)
James Feistc6248a52018-08-14 10:09:45 -070047{
48 return std::find(settableInterfaces.begin(), settableInterfaces.end(),
49 interface) != settableInterfaces.end()
50 ? sdbusplus::asio::PropertyPermission::readWrite
51 : sdbusplus::asio::PropertyPermission::readOnly;
52}
53
Christopher Meiscf6a75b2025-06-03 07:53:50 +020054EntityManager::EntityManager(
Alexander Hansena555acf2025-06-27 11:59:10 +020055 std::shared_ptr<sdbusplus::asio::connection>& systemBus,
56 boost::asio::io_context& io) :
Christopher Meiscf6a75b2025-06-03 07:53:50 +020057 systemBus(systemBus),
58 objServer(sdbusplus::asio::object_server(systemBus, /*skipManager=*/true)),
59 lastJson(nlohmann::json::object()),
Alexander Hansenb1340da2025-06-27 14:29:13 +020060 systemConfiguration(nlohmann::json::object()), io(io),
Alexander Hansene1646272025-07-25 11:47:17 +020061 powerStatus(*systemBus), propertiesChangedTimer(io)
Christopher Meiscf6a75b2025-06-03 07:53:50 +020062{
63 // All other objects that EntityManager currently support are under the
64 // inventory subtree.
65 // See the discussion at
66 // https://discord.com/channels/775381525260664832/1018929092009144380
67 objServer.add_manager("/xyz/openbmc_project/inventory");
James Feist75fdeeb2018-02-20 14:26:16 -080068
Christopher Meiscf6a75b2025-06-03 07:53:50 +020069 entityIface = objServer.add_interface("/xyz/openbmc_project/EntityManager",
70 "xyz.openbmc_project.EntityManager");
71 entityIface->register_method("ReScan", [this]() {
72 propertiesChangedCallback();
73 });
74 dbus_interface::tryIfaceInitialize(entityIface);
Christopher Meisf7252572025-06-11 13:22:05 +020075
76 initFilters(configuration.probeInterfaces);
Christopher Meiscf6a75b2025-06-03 07:53:50 +020077}
78
79void EntityManager::postToDbus(const nlohmann::json& newConfiguration)
James Feist1b2e2242018-01-30 13:45:19 -080080{
Matt Spinler6eb60972023-08-14 16:36:20 -050081 std::map<std::string, std::string> newBoards; // path -> name
Benjamin Fairca2eb042022-09-13 06:40:42 +000082
James Feist97a63f12018-05-17 13:50:57 -070083 // iterate through boards
Patrick Williams2594d362022-09-28 06:46:24 -050084 for (const auto& [boardId, boardConfig] : newConfiguration.items())
James Feist1b2e2242018-01-30 13:45:19 -080085 {
Alexander Hansen4fcd9462025-07-30 17:44:44 +020086 postBoardToDBus(boardId, boardConfig, newBoards);
James Feist1b2e2242018-01-30 13:45:19 -080087 }
Benjamin Fairca2eb042022-09-13 06:40:42 +000088
Matt Spinler6eb60972023-08-14 16:36:20 -050089 for (const auto& [assocPath, assocPropValue] :
90 topology.getAssocs(newBoards))
Benjamin Fairca2eb042022-09-13 06:40:42 +000091 {
Matt Spinler6eb60972023-08-14 16:36:20 -050092 auto findBoard = newBoards.find(assocPath);
93 if (findBoard == newBoards.end())
94 {
95 continue;
96 }
Benjamin Fairca2eb042022-09-13 06:40:42 +000097
Alexander Hansen57604ed2025-06-27 13:22:28 +020098 auto ifacePtr = dbus_interface.createInterface(
Matt Spinler6eb60972023-08-14 16:36:20 -050099 objServer, assocPath, "xyz.openbmc_project.Association.Definitions",
100 findBoard->second);
101
102 ifacePtr->register_property("Associations", assocPropValue);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200103 dbus_interface::tryIfaceInitialize(ifacePtr);
Benjamin Fairca2eb042022-09-13 06:40:42 +0000104 }
James Feist1b2e2242018-01-30 13:45:19 -0800105}
106
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200107void EntityManager::postBoardToDBus(
108 const std::string& boardId, const nlohmann::json& boardConfig,
109 std::map<std::string, std::string>& newBoards)
110{
111 std::string boardName = boardConfig["Name"];
112 std::string boardNameOrig = boardConfig["Name"];
113 std::string jsonPointerPath = "/" + boardId;
114 // loop through newConfiguration, but use values from system
115 // configuration to be able to modify via dbus later
116 auto boardValues = systemConfiguration[boardId];
117 auto findBoardType = boardValues.find("Type");
118 std::string boardType;
119 if (findBoardType != boardValues.end() &&
120 findBoardType->type() == nlohmann::json::value_t::string)
121 {
122 boardType = findBoardType->get<std::string>();
123 std::regex_replace(boardType.begin(), boardType.begin(),
124 boardType.end(), illegalDbusMemberRegex, "_");
125 }
126 else
127 {
128 std::cerr << "Unable to find type for " << boardName
129 << " reverting to Chassis.\n";
130 boardType = "Chassis";
131 }
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200132
Christopher Meis811160e2025-08-08 08:48:37 +0200133 const std::string boardPath =
134 em_utils::buildInventorySystemPath(boardName, boardType);
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200135
136 std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
137 dbus_interface.createInterface(objServer, boardPath,
138 "xyz.openbmc_project.Inventory.Item",
139 boardName);
140
141 std::shared_ptr<sdbusplus::asio::dbus_interface> boardIface =
142 dbus_interface.createInterface(
143 objServer, boardPath,
144 "xyz.openbmc_project.Inventory.Item." + boardType, boardNameOrig);
145
146 dbus_interface.createAddObjectMethod(
147 io, jsonPointerPath, boardPath, systemConfiguration, objServer,
148 boardNameOrig);
149
150 dbus_interface::populateInterfaceFromJson(
151 io, systemConfiguration, jsonPointerPath, boardIface, boardValues,
152 objServer);
153 jsonPointerPath += "/";
154 // iterate through board properties
155 for (const auto& [propName, propValue] : boardValues.items())
156 {
157 if (propValue.type() == nlohmann::json::value_t::object)
158 {
159 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
160 dbus_interface.createInterface(objServer, boardPath, propName,
161 boardNameOrig);
162
163 dbus_interface::populateInterfaceFromJson(
164 io, systemConfiguration, jsonPointerPath + propName, iface,
165 propValue, objServer);
166 }
167 }
168
Alexander Hansen4fa40122025-07-30 18:26:35 +0200169 nlohmann::json::iterator exposes = boardValues.find("Exposes");
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200170 if (exposes == boardValues.end())
171 {
172 return;
173 }
174 // iterate through exposes
175 jsonPointerPath += "Exposes/";
176
177 // store the board level pointer so we can modify it on the way down
178 std::string jsonPointerPathBoard = jsonPointerPath;
179 size_t exposesIndex = -1;
Alexander Hansen4fa40122025-07-30 18:26:35 +0200180 for (nlohmann::json& item : *exposes)
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200181 {
Alexander Hansen4fa40122025-07-30 18:26:35 +0200182 postExposesRecordsToDBus(item, exposesIndex, boardNameOrig,
183 jsonPointerPath, jsonPointerPathBoard,
184 boardPath, boardType);
185 }
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200186
Alexander Hansen4fa40122025-07-30 18:26:35 +0200187 newBoards.emplace(boardPath, boardNameOrig);
188}
189
190void EntityManager::postExposesRecordsToDBus(
191 nlohmann::json& item, size_t& exposesIndex,
192 const std::string& boardNameOrig, std::string jsonPointerPath,
193 const std::string& jsonPointerPathBoard, const std::string& boardPath,
194 const std::string& boardType)
195{
196 exposesIndex++;
197 jsonPointerPath = jsonPointerPathBoard;
198 jsonPointerPath += std::to_string(exposesIndex);
199
200 auto findName = item.find("Name");
201 if (findName == item.end())
202 {
203 std::cerr << "cannot find name in field " << item << "\n";
204 return;
205 }
206 auto findStatus = item.find("Status");
207 // if status is not found it is assumed to be status = 'okay'
208 if (findStatus != item.end())
209 {
210 if (*findStatus == "disabled")
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200211 {
Alexander Hansen4fa40122025-07-30 18:26:35 +0200212 return;
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200213 }
Alexander Hansen4fa40122025-07-30 18:26:35 +0200214 }
215 auto findType = item.find("Type");
216 std::string itemType;
217 if (findType != item.end())
218 {
219 itemType = findType->get<std::string>();
220 std::regex_replace(itemType.begin(), itemType.begin(), itemType.end(),
221 illegalDbusPathRegex, "_");
222 }
223 else
224 {
225 itemType = "unknown";
226 }
227 std::string itemName = findName->get<std::string>();
228 std::regex_replace(itemName.begin(), itemName.begin(), itemName.end(),
229 illegalDbusMemberRegex, "_");
230 std::string ifacePath = boardPath;
231 ifacePath += "/";
232 ifacePath += itemName;
233
234 if (itemType == "BMC")
235 {
236 std::shared_ptr<sdbusplus::asio::dbus_interface> bmcIface =
237 dbus_interface.createInterface(
238 objServer, ifacePath, "xyz.openbmc_project.Inventory.Item.Bmc",
239 boardNameOrig);
240 dbus_interface::populateInterfaceFromJson(
241 io, systemConfiguration, jsonPointerPath, bmcIface, item, objServer,
242 getPermission(itemType));
243 }
244 else if (itemType == "System")
245 {
246 std::shared_ptr<sdbusplus::asio::dbus_interface> systemIface =
247 dbus_interface.createInterface(
248 objServer, ifacePath,
249 "xyz.openbmc_project.Inventory.Item.System", boardNameOrig);
250 dbus_interface::populateInterfaceFromJson(
251 io, systemConfiguration, jsonPointerPath, systemIface, item,
252 objServer, getPermission(itemType));
253 }
254
255 for (const auto& [name, config] : item.items())
256 {
257 jsonPointerPath = jsonPointerPathBoard;
258 jsonPointerPath.append(std::to_string(exposesIndex))
259 .append("/")
260 .append(name);
Alexander Hansen5aa65d82025-07-31 14:08:42 +0200261
262 if (!postConfigurationRecord(name, config, boardNameOrig, itemType,
263 jsonPointerPath, ifacePath))
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200264 {
Alexander Hansen5aa65d82025-07-31 14:08:42 +0200265 break;
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200266 }
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200267 }
268
Alexander Hansen4fa40122025-07-30 18:26:35 +0200269 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface =
270 dbus_interface.createInterface(
271 objServer, ifacePath,
272 "xyz.openbmc_project.Configuration." + itemType, boardNameOrig);
273
274 dbus_interface::populateInterfaceFromJson(
275 io, systemConfiguration, jsonPointerPath, itemIface, item, objServer,
276 getPermission(itemType));
277
278 topology.addBoard(boardPath, boardType, boardNameOrig, item);
Alexander Hansen4fcd9462025-07-30 17:44:44 +0200279}
280
Alexander Hansen5aa65d82025-07-31 14:08:42 +0200281bool EntityManager::postConfigurationRecord(
282 const std::string& name, nlohmann::json& config,
283 const std::string& boardNameOrig, const std::string& itemType,
284 const std::string& jsonPointerPath, const std::string& ifacePath)
285{
286 if (config.type() == nlohmann::json::value_t::object)
287 {
288 std::string ifaceName = "xyz.openbmc_project.Configuration.";
289 ifaceName.append(itemType).append(".").append(name);
290
291 std::shared_ptr<sdbusplus::asio::dbus_interface> objectIface =
292 dbus_interface.createInterface(objServer, ifacePath, ifaceName,
293 boardNameOrig);
294
295 dbus_interface::populateInterfaceFromJson(
296 io, systemConfiguration, jsonPointerPath, objectIface, config,
297 objServer, getPermission(name));
298 }
299 else if (config.type() == nlohmann::json::value_t::array)
300 {
301 size_t index = 0;
302 if (config.empty())
303 {
304 return true;
305 }
306 bool isLegal = true;
307 auto type = config[0].type();
308 if (type != nlohmann::json::value_t::object)
309 {
310 return true;
311 }
312
313 // verify legal json
314 for (const auto& arrayItem : config)
315 {
316 if (arrayItem.type() != type)
317 {
318 isLegal = false;
319 break;
320 }
321 }
322 if (!isLegal)
323 {
324 std::cerr << "dbus format error" << config << "\n";
325 return false;
326 }
327
328 for (auto& arrayItem : config)
329 {
330 std::string ifaceName = "xyz.openbmc_project.Configuration.";
331 ifaceName.append(itemType).append(".").append(name);
332 ifaceName.append(std::to_string(index));
333
334 std::shared_ptr<sdbusplus::asio::dbus_interface> objectIface =
335 dbus_interface.createInterface(objServer, ifacePath, ifaceName,
336 boardNameOrig);
337
338 dbus_interface::populateInterfaceFromJson(
339 io, systemConfiguration,
340 jsonPointerPath + "/" + std::to_string(index), objectIface,
341 arrayItem, objServer, getPermission(name));
342 index++;
343 }
344 }
345
346 return true;
347}
348
Andrew Jeffery55192932022-03-24 12:29:27 +1030349static bool deviceRequiresPowerOn(const nlohmann::json& entity)
350{
351 auto powerState = entity.find("PowerState");
Andrew Jefferyb6209442022-03-24 12:36:20 +1030352 if (powerState == entity.end())
Andrew Jeffery55192932022-03-24 12:29:27 +1030353 {
Andrew Jefferyb6209442022-03-24 12:36:20 +1030354 return false;
Andrew Jeffery55192932022-03-24 12:29:27 +1030355 }
356
Ed Tanous3013fb42022-07-09 08:27:06 -0700357 const auto* ptr = powerState->get_ptr<const std::string*>();
358 if (ptr == nullptr)
Andrew Jefferyb6209442022-03-24 12:36:20 +1030359 {
360 return false;
361 }
362
363 return *ptr == "On" || *ptr == "BiosPost";
Andrew Jeffery55192932022-03-24 12:29:27 +1030364}
365
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030366static void pruneDevice(const nlohmann::json& systemConfiguration,
367 const bool powerOff, const bool scannedPowerOff,
368 const std::string& name, const nlohmann::json& device)
369{
370 if (systemConfiguration.contains(name))
371 {
372 return;
373 }
374
Andrew Jeffery4db38bc2022-03-24 13:42:41 +1030375 if (deviceRequiresPowerOn(device) && (powerOff || scannedPowerOff))
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030376 {
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030377 return;
378 }
379
380 logDeviceRemoved(device);
381}
382
Alexander Hansen95ab18f2025-06-27 13:58:10 +0200383void EntityManager::startRemovedTimer(boost::asio::steady_timer& timer,
384 nlohmann::json& systemConfiguration)
James Feist1df06a42019-04-11 14:23:04 -0700385{
James Feistfb00f392019-06-25 14:16:48 -0700386 if (systemConfiguration.empty() || lastJson.empty())
387 {
388 return; // not ready yet
389 }
James Feist1df06a42019-04-11 14:23:04 -0700390 if (scannedPowerOn)
391 {
392 return;
393 }
394
Alexander Hansen0ab32b32025-06-27 14:50:33 +0200395 if (!powerStatus.isPowerOn() && scannedPowerOff)
James Feist1df06a42019-04-11 14:23:04 -0700396 {
397 return;
398 }
399
James Feistb1728ca2020-04-30 15:40:55 -0700400 timer.expires_after(std::chrono::seconds(10));
Andrew Jeffery27a1cfb2022-03-24 12:31:53 +1030401 timer.async_wait(
Alexander Hansen95ab18f2025-06-27 13:58:10 +0200402 [&systemConfiguration, this](const boost::system::error_code& ec) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400403 if (ec == boost::asio::error::operation_aborted)
404 {
405 return;
406 }
Andrew Jeffery27a1cfb2022-03-24 12:31:53 +1030407
Alexander Hansen0ab32b32025-06-27 14:50:33 +0200408 bool powerOff = !powerStatus.isPowerOn();
Patrick Williamsb7077432024-08-16 15:22:21 -0400409 for (const auto& [name, device] : lastJson.items())
410 {
411 pruneDevice(systemConfiguration, powerOff, scannedPowerOff,
412 name, device);
413 }
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030414
Patrick Williamsb7077432024-08-16 15:22:21 -0400415 scannedPowerOff = true;
416 if (!powerOff)
417 {
418 scannedPowerOn = true;
419 }
420 });
James Feist1df06a42019-04-11 14:23:04 -0700421}
422
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200423void EntityManager::pruneConfiguration(bool powerOff, const std::string& name,
424 const nlohmann::json& device)
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030425{
426 if (powerOff && deviceRequiresPowerOn(device))
427 {
428 // power not on yet, don't know if it's there or not
429 return;
430 }
431
Alexander Hansen57604ed2025-06-27 13:22:28 +0200432 auto& ifaces = dbus_interface.getDeviceInterfaces(device);
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030433 for (auto& iface : ifaces)
434 {
435 auto sharedPtr = iface.lock();
436 if (!!sharedPtr)
437 {
438 objServer.remove_interface(sharedPtr);
439 }
440 }
441
442 ifaces.clear();
443 systemConfiguration.erase(name);
Matt Spinler6eb60972023-08-14 16:36:20 -0500444 topology.remove(device["Name"].get<std::string>());
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030445 logDeviceRemoved(device);
446}
447
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200448void EntityManager::publishNewConfiguration(
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030449 const size_t& instance, const size_t count,
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200450 boost::asio::steady_timer& timer, // Gerrit discussion:
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030451 // https://gerrit.openbmc-project.xyz/c/openbmc/entity-manager/+/52316/6
452 //
453 // Discord discussion:
454 // https://discord.com/channels/775381525260664832/867820390406422538/958048437729910854
455 //
456 // NOLINTNEXTLINE(performance-unnecessary-value-param)
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200457 const nlohmann::json newConfiguration)
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030458{
Alexander Hansena555acf2025-06-27 11:59:10 +0200459 loadOverlays(newConfiguration, io);
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030460
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200461 boost::asio::post(io, [this]() {
Christopher Meisf7252572025-06-11 13:22:05 +0200462 if (!writeJsonFiles(systemConfiguration))
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030463 {
464 std::cerr << "Error writing json files\n";
465 }
466 });
467
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200468 boost::asio::post(io, [this, &instance, count, &timer, newConfiguration]() {
469 postToDbus(newConfiguration);
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030470 if (count == instance)
471 {
Alexander Hansen95ab18f2025-06-27 13:58:10 +0200472 startRemovedTimer(timer, systemConfiguration);
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030473 }
474 });
475}
476
James Feist8f2710a2018-05-09 17:18:55 -0700477// main properties changed entry
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200478void EntityManager::propertiesChangedCallback()
James Feist8f2710a2018-05-09 17:18:55 -0700479{
Alexander Hansenfc1b1e22025-06-27 14:34:11 +0200480 propertiesChangedInstance++;
481 size_t count = propertiesChangedInstance;
James Feist1df06a42019-04-11 14:23:04 -0700482
Alexander Hansenb1340da2025-06-27 14:29:13 +0200483 propertiesChangedTimer.expires_after(std::chrono::milliseconds(500));
James Feist8f2710a2018-05-09 17:18:55 -0700484
485 // setup an async wait as we normally get flooded with new requests
Alexander Hansenb1340da2025-06-27 14:29:13 +0200486 propertiesChangedTimer.async_wait(
487 [this, count](const boost::system::error_code& ec) {
488 if (ec == boost::asio::error::operation_aborted)
489 {
490 // we were cancelled
491 return;
492 }
493 if (ec)
494 {
495 std::cerr << "async wait error " << ec << "\n";
496 return;
497 }
James Feist8f2710a2018-05-09 17:18:55 -0700498
Alexander Hansenb1340da2025-06-27 14:29:13 +0200499 if (propertiesChangedInProgress)
500 {
501 propertiesChangedCallback();
502 return;
503 }
504 propertiesChangedInProgress = true;
James Feist2539ccd2020-05-01 16:15:08 -0700505
Alexander Hansenb1340da2025-06-27 14:29:13 +0200506 nlohmann::json oldConfiguration = systemConfiguration;
507 auto missingConfigurations = std::make_shared<nlohmann::json>();
508 *missingConfigurations = systemConfiguration;
James Feist899e17f2019-09-13 11:46:29 -0700509
Alexander Hansenb1340da2025-06-27 14:29:13 +0200510 auto perfScan = std::make_shared<scan::PerformScan>(
Christopher Meisf7252572025-06-11 13:22:05 +0200511 *this, *missingConfigurations, configuration.configurations, io,
Alexander Hansenb1340da2025-06-27 14:29:13 +0200512 [this, count, oldConfiguration, missingConfigurations]() {
513 // this is something that since ac has been applied to the
514 // bmc we saw, and we no longer see it
Alexander Hansen0ab32b32025-06-27 14:50:33 +0200515 bool powerOff = !powerStatus.isPowerOn();
Alexander Hansenb1340da2025-06-27 14:29:13 +0200516 for (const auto& [name, device] :
517 missingConfigurations->items())
518 {
519 pruneConfiguration(powerOff, name, device);
520 }
Alexander Hansenb1340da2025-06-27 14:29:13 +0200521 nlohmann::json newConfiguration = systemConfiguration;
522
Christopher Meisf7252572025-06-11 13:22:05 +0200523 deriveNewConfiguration(oldConfiguration, newConfiguration);
Alexander Hansenb1340da2025-06-27 14:29:13 +0200524
525 for (const auto& [_, device] : newConfiguration.items())
526 {
527 logDeviceAdded(device);
528 }
529
530 propertiesChangedInProgress = false;
531
532 boost::asio::post(io, [this, newConfiguration, count] {
533 publishNewConfiguration(
Alexander Hansenfc1b1e22025-06-27 14:34:11 +0200534 std::ref(propertiesChangedInstance), count,
Alexander Hansenb1340da2025-06-27 14:29:13 +0200535 std::ref(propertiesChangedTimer), newConfiguration);
536 });
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200537 });
Alexander Hansenb1340da2025-06-27 14:29:13 +0200538 perfScan->run();
539 });
James Feist75fdeeb2018-02-20 14:26:16 -0800540}
541
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600542// Check if InterfacesAdded payload contains an iface that needs probing.
Patrick Williamsb7077432024-08-16 15:22:21 -0400543static bool iaContainsProbeInterface(
Christopher Meisf7252572025-06-11 13:22:05 +0200544 sdbusplus::message_t& msg,
545 const std::unordered_set<std::string>& probeInterfaces)
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600546{
547 sdbusplus::message::object_path path;
548 DBusObject interfaces;
549 std::set<std::string> interfaceSet;
550 std::set<std::string> intersect;
551
552 msg.read(path, interfaces);
553
554 std::for_each(interfaces.begin(), interfaces.end(),
555 [&interfaceSet](const auto& iface) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400556 interfaceSet.insert(iface.first);
557 });
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600558
559 std::set_intersection(interfaceSet.begin(), interfaceSet.end(),
560 probeInterfaces.begin(), probeInterfaces.end(),
561 std::inserter(intersect, intersect.end()));
562 return !intersect.empty();
563}
564
565// Check if InterfacesRemoved payload contains an iface that needs probing.
Patrick Williamsb7077432024-08-16 15:22:21 -0400566static bool irContainsProbeInterface(
Christopher Meisf7252572025-06-11 13:22:05 +0200567 sdbusplus::message_t& msg,
568 const std::unordered_set<std::string>& probeInterfaces)
Matt Spinler8d1ac3a2023-02-02 09:48:04 -0600569{
570 sdbusplus::message::object_path path;
571 std::set<std::string> interfaces;
572 std::set<std::string> intersect;
573
574 msg.read(path, interfaces);
575
576 std::set_intersection(interfaces.begin(), interfaces.end(),
577 probeInterfaces.begin(), probeInterfaces.end(),
578 std::inserter(intersect, intersect.end()));
579 return !intersect.empty();
580}
581
Alexander Hansenad28e402025-06-25 12:38:20 +0200582void EntityManager::handleCurrentConfigurationJson()
583{
584 if (em_utils::fwVersionIsSame())
585 {
Christopher Meisf7252572025-06-11 13:22:05 +0200586 if (std::filesystem::is_regular_file(currentConfiguration))
Alexander Hansenad28e402025-06-25 12:38:20 +0200587 {
588 // this file could just be deleted, but it's nice for debug
589 std::filesystem::create_directory(tempConfigDir);
590 std::filesystem::remove(lastConfiguration);
Christopher Meisf7252572025-06-11 13:22:05 +0200591 std::filesystem::copy(currentConfiguration, lastConfiguration);
592 std::filesystem::remove(currentConfiguration);
Alexander Hansenad28e402025-06-25 12:38:20 +0200593
594 std::ifstream jsonStream(lastConfiguration);
595 if (jsonStream.good())
596 {
597 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
598 if (data.is_discarded())
599 {
600 std::cerr
601 << "syntax error in " << lastConfiguration << "\n";
602 }
603 else
604 {
605 lastJson = std::move(data);
606 }
607 }
608 else
609 {
610 std::cerr << "unable to open " << lastConfiguration << "\n";
611 }
612 }
613 }
614 else
615 {
616 // not an error, just logging at this level to make it in the journal
617 std::cerr << "Clearing previous configuration\n";
Christopher Meisf7252572025-06-11 13:22:05 +0200618 std::filesystem::remove(currentConfiguration);
Alexander Hansenad28e402025-06-25 12:38:20 +0200619 }
620}
621
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200622void EntityManager::registerCallback(const std::string& path)
James Feist75fdeeb2018-02-20 14:26:16 -0800623{
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200624 if (dbusMatches.contains(path))
625 {
626 return;
627 }
Nan Zhoua3315672022-09-20 19:48:14 +0000628
Alexander Hansen60803182025-06-27 14:41:28 +0200629 lg2::debug("creating PropertiesChanged match on {PATH}", "PATH", path);
630
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200631 std::function<void(sdbusplus::message_t & message)> eventHandler =
632 [&](sdbusplus::message_t&) { propertiesChangedCallback(); };
James Feistfd1264a2018-05-03 12:10:00 -0700633
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200634 sdbusplus::bus::match_t match(
635 static_cast<sdbusplus::bus_t&>(*systemBus),
636 "type='signal',member='PropertiesChanged',path='" + path + "'",
637 eventHandler);
638 dbusMatches.emplace(path, std::move(match));
639}
James Feistfd1264a2018-05-03 12:10:00 -0700640
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200641// We need a poke from DBus for static providers that create all their
642// objects prior to claiming a well-known name, and thus don't emit any
643// org.freedesktop.DBus.Properties signals. Similarly if a process exits
644// for any reason, expected or otherwise, we'll need a poke to remove
645// entities from DBus.
Christopher Meisf7252572025-06-11 13:22:05 +0200646void EntityManager::initFilters(
647 const std::unordered_set<std::string>& probeInterfaces)
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200648{
Alexander Hansen0f7bd782025-06-27 13:39:53 +0200649 nameOwnerChangedMatch = std::make_unique<sdbusplus::bus::match_t>(
Patrick Williams2af39222022-07-22 19:26:56 -0500650 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishopc76af0f2020-12-04 13:50:23 -0500651 sdbusplus::bus::match::rules::nameOwnerChanged(),
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200652 [this](sdbusplus::message_t& m) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400653 auto [name, oldOwner,
654 newOwner] = m.unpack<std::string, std::string, std::string>();
Patrick Williams7b8786f2022-10-10 10:23:37 -0500655
Patrick Williamsb7077432024-08-16 15:22:21 -0400656 if (name.starts_with(':'))
657 {
658 // We should do nothing with unique-name connections.
659 return;
660 }
Patrick Williams7b8786f2022-10-10 10:23:37 -0500661
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200662 propertiesChangedCallback();
Patrick Williamsb7077432024-08-16 15:22:21 -0400663 });
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200664
Brad Bishop10a8c5f2020-12-07 21:40:07 -0500665 // We also need a poke from DBus when new interfaces are created or
666 // destroyed.
Alexander Hansen0f7bd782025-06-27 13:39:53 +0200667 interfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
Patrick Williams2af39222022-07-22 19:26:56 -0500668 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishop10a8c5f2020-12-07 21:40:07 -0500669 sdbusplus::bus::match::rules::interfacesAdded(),
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200670 [this, probeInterfaces](sdbusplus::message_t& msg) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400671 if (iaContainsProbeInterface(msg, probeInterfaces))
672 {
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200673 propertiesChangedCallback();
Patrick Williamsb7077432024-08-16 15:22:21 -0400674 }
675 });
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200676
Alexander Hansen0f7bd782025-06-27 13:39:53 +0200677 interfacesRemovedMatch = std::make_unique<sdbusplus::bus::match_t>(
Patrick Williams2af39222022-07-22 19:26:56 -0500678 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishop10a8c5f2020-12-07 21:40:07 -0500679 sdbusplus::bus::match::rules::interfacesRemoved(),
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200680 [this, probeInterfaces](sdbusplus::message_t& msg) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400681 if (irContainsProbeInterface(msg, probeInterfaces))
682 {
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200683 propertiesChangedCallback();
Patrick Williamsb7077432024-08-16 15:22:21 -0400684 }
685 });
Christopher Meiscf6a75b2025-06-03 07:53:50 +0200686}