blob: 187373a3b5c6e9f3e7a77074369fa3d538e46c39 [file] [log] [blame]
James Feist3cb5fec2018-01-23 14:41:51 -08001/*
2// Copyright (c) 2018 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
Brad Bishope45d8c72022-05-25 15:12:53 -040016/// \file entity_manager.cpp
James Feist3cb5fec2018-01-23 14:41:51 -080017
Brad Bishope45d8c72022-05-25 15:12:53 -040018#include "entity_manager.hpp"
James Feist1df06a42019-04-11 14:23:04 -070019
Brad Bishope45d8c72022-05-25 15:12:53 -040020#include "overlay.hpp"
Christopher Meis26fbbd52025-03-26 14:55:06 +010021#include "perform_probe.hpp"
22#include "perform_scan.hpp"
Benjamin Fairca2eb042022-09-13 06:40:42 +000023#include "topology.hpp"
Brad Bishope45d8c72022-05-25 15:12:53 -040024#include "utils.hpp"
25#include "variant_visitors.hpp"
James Feist481c5d52019-08-13 14:40:40 -070026
James Feist11be6672018-04-06 14:05:32 -070027#include <boost/algorithm/string/case_conv.hpp>
James Feistf5125b02019-06-06 11:27:43 -070028#include <boost/algorithm/string/classification.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080029#include <boost/algorithm/string/predicate.hpp>
30#include <boost/algorithm/string/replace.hpp>
James Feistf5125b02019-06-06 11:27:43 -070031#include <boost/algorithm/string/split.hpp>
James Feist02d2b932020-02-06 16:28:48 -080032#include <boost/asio/io_context.hpp>
Ed Tanous49a888c2023-03-06 13:44:51 -080033#include <boost/asio/post.hpp>
James Feistb1728ca2020-04-30 15:40:55 -070034#include <boost/asio/steady_timer.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080035#include <boost/container/flat_map.hpp>
36#include <boost/container/flat_set.hpp>
James Feistf5125b02019-06-06 11:27:43 -070037#include <boost/range/iterator_range.hpp>
James Feist8c505da2020-05-28 10:06:33 -070038#include <nlohmann/json.hpp>
39#include <sdbusplus/asio/connection.hpp>
40#include <sdbusplus/asio/object_server.hpp>
41
Igor Kononenko9fd87e52020-10-06 01:18:17 +030042#include <charconv>
James Feist637b3ef2019-04-15 16:35:30 -070043#include <filesystem>
James Feista465ccc2019-02-08 12:51:01 -080044#include <fstream>
Andrew Jefferye35d0ac2022-03-24 15:53:13 +103045#include <functional>
James Feista465ccc2019-02-08 12:51:01 -080046#include <iostream>
Andrew Jeffery666583b2021-12-01 15:50:12 +103047#include <map>
James Feista465ccc2019-02-08 12:51:01 -080048#include <regex>
James Feista465ccc2019-02-08 12:51:01 -080049#include <variant>
Andrew Jefferya9c58922021-06-01 09:28:59 +093050constexpr const char* hostConfigurationDirectory = SYSCONF_DIR "configurations";
James Feista465ccc2019-02-08 12:51:01 -080051constexpr const char* configurationDirectory = PACKAGE_DIR "configurations";
52constexpr const char* schemaDirectory = PACKAGE_DIR "configurations/schemas";
James Feist1df06a42019-04-11 14:23:04 -070053constexpr const char* tempConfigDir = "/tmp/configuration/";
54constexpr const char* lastConfiguration = "/tmp/configuration/last.json";
55constexpr const char* currentConfiguration = "/var/configuration/system.json";
James Feista465ccc2019-02-08 12:51:01 -080056constexpr const char* globalSchema = "global.json";
James Feistf1b14142019-04-10 15:22:09 -070057
Adrian Ambrożewiczc789fca2020-05-14 15:50:05 +020058static constexpr std::array<const char*, 6> settableInterfaces = {
59 "FanProfile", "Pid", "Pid.Zone", "Stepwise", "Thresholds", "Polling"};
James Feist68500ff2018-08-08 15:40:42 -070060using JsonVariantType =
James Feist338b8a72019-03-01 10:16:45 -080061 std::variant<std::vector<std::string>, std::vector<double>, std::string,
62 int64_t, uint64_t, double, int32_t, uint32_t, int16_t,
63 uint16_t, uint8_t, bool>;
James Feist3cb5fec2018-01-23 14:41:51 -080064
Ed Tanousfc171422024-04-04 17:18:16 -070065// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
James Feistd58879a2019-09-11 11:26:07 -070066// store reference to all interfaces so we can destroy them later
67boost::container::flat_map<
James Feist02d2b932020-02-06 16:28:48 -080068 std::string, std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>>
James Feistd58879a2019-09-11 11:26:07 -070069 inventory;
70
James Feist3cb5fec2018-01-23 14:41:51 -080071// todo: pass this through nicer
Ed Tanous07d467b2021-02-23 14:48:37 -080072std::shared_ptr<sdbusplus::asio::connection> systemBus;
Andrew Jeffery47af65a2021-12-01 14:16:31 +103073nlohmann::json lastJson;
Matt Spinler6eb60972023-08-14 16:36:20 -050074Topology topology;
James Feist3cb5fec2018-01-23 14:41:51 -080075
James Feist02d2b932020-02-06 16:28:48 -080076boost::asio::io_context io;
Ed Tanousfc171422024-04-04 17:18:16 -070077// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
James Feist02d2b932020-02-06 16:28:48 -080078
Ed Tanous07d467b2021-02-23 14:48:37 -080079const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]");
80const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]");
James Feist1b2e2242018-01-30 13:45:19 -080081
John Edward Broadbentd97c6312023-10-26 20:32:07 +000082void tryIfaceInitialize(std::shared_ptr<sdbusplus::asio::dbus_interface>& iface)
83{
84 try
85 {
86 iface->initialize();
87 }
88 catch (std::exception& e)
89 {
90 std::cerr << "Unable to initialize dbus interface : " << e.what()
91 << "\n"
92 << "object Path : " << iface->get_object_path() << "\n"
93 << "interface name : " << iface->get_interface_name() << "\n";
94 }
95}
96
Patrick Williams5a807032025-03-03 11:20:39 -050097static std::shared_ptr<sdbusplus::asio::dbus_interface> createInterface(
98 sdbusplus::asio::object_server& objServer, const std::string& path,
99 const std::string& interface, const std::string& parent,
100 bool checkNull = false)
James Feistd58879a2019-09-11 11:26:07 -0700101{
James Feist02d2b932020-02-06 16:28:48 -0800102 // on first add we have no reason to check for null before add, as there
103 // won't be any. For dynamically added interfaces, we check for null so that
104 // a constant delete/add will not create a memory leak
105
106 auto ptr = objServer.add_interface(path, interface);
107 auto& dataVector = inventory[parent];
108 if (checkNull)
109 {
110 auto it = std::find_if(dataVector.begin(), dataVector.end(),
111 [](const auto& p) { return p.expired(); });
112 if (it != dataVector.end())
113 {
114 *it = ptr;
115 return ptr;
116 }
117 }
118 dataVector.emplace_back(ptr);
119 return ptr;
James Feistd58879a2019-09-11 11:26:07 -0700120}
121
James Feist8f2710a2018-05-09 17:18:55 -0700122// writes output files to persist data
James Feista465ccc2019-02-08 12:51:01 -0800123bool writeJsonFiles(const nlohmann::json& systemConfiguration)
James Feist1b2e2242018-01-30 13:45:19 -0800124{
James Feist1df06a42019-04-11 14:23:04 -0700125 std::filesystem::create_directory(configurationOutDir);
126 std::ofstream output(currentConfiguration);
James Feistbb43d022018-06-12 15:44:33 -0700127 if (!output.good())
128 {
129 return false;
130 }
James Feist1b2e2242018-01-30 13:45:19 -0800131 output << systemConfiguration.dump(4);
132 output.close();
James Feistbb43d022018-06-12 15:44:33 -0700133 return true;
James Feist8f2710a2018-05-09 17:18:55 -0700134}
James Feist1b2e2242018-01-30 13:45:19 -0800135
James Feist97a63f12018-05-17 13:50:57 -0700136template <typename JsonType>
James Feista465ccc2019-02-08 12:51:01 -0800137bool setJsonFromPointer(const std::string& ptrStr, const JsonType& value,
138 nlohmann::json& systemConfiguration)
James Feist97a63f12018-05-17 13:50:57 -0700139{
140 try
141 {
142 nlohmann::json::json_pointer ptr(ptrStr);
James Feista465ccc2019-02-08 12:51:01 -0800143 nlohmann::json& ref = systemConfiguration[ptr];
James Feist97a63f12018-05-17 13:50:57 -0700144 ref = value;
145 return true;
146 }
James Feist98132792019-07-09 13:29:09 -0700147 catch (const std::out_of_range&)
James Feist97a63f12018-05-17 13:50:57 -0700148 {
149 return false;
150 }
151}
James Feistbb43d022018-06-12 15:44:33 -0700152
James Feistebcc26b2019-03-22 12:30:43 -0700153// template function to add array as dbus property
154template <typename PropertyType>
155void addArrayToDbus(const std::string& name, const nlohmann::json& array,
156 sdbusplus::asio::dbus_interface* iface,
157 sdbusplus::asio::PropertyPermission permission,
158 nlohmann::json& systemConfiguration,
159 const std::string& jsonPointerString)
160{
161 std::vector<PropertyType> values;
162 for (const auto& property : array)
163 {
164 auto ptr = property.get_ptr<const PropertyType*>();
165 if (ptr != nullptr)
166 {
167 values.emplace_back(*ptr);
168 }
169 }
170
171 if (permission == sdbusplus::asio::PropertyPermission::readOnly)
172 {
173 iface->register_property(name, values);
174 }
175 else
176 {
177 iface->register_property(
178 name, values,
179 [&systemConfiguration,
180 jsonPointerString{std::string(jsonPointerString)}](
181 const std::vector<PropertyType>& newVal,
182 std::vector<PropertyType>& val) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400183 val = newVal;
184 if (!setJsonFromPointer(jsonPointerString, val,
185 systemConfiguration))
186 {
187 std::cerr << "error setting json field\n";
188 return -1;
189 }
190 if (!writeJsonFiles(systemConfiguration))
191 {
192 std::cerr << "error setting json file\n";
193 return -1;
194 }
195 return 1;
196 });
James Feistebcc26b2019-03-22 12:30:43 -0700197 }
198}
199
James Feistbb43d022018-06-12 15:44:33 -0700200template <typename PropertyType>
Ed Tanous3013fb42022-07-09 08:27:06 -0700201void addProperty(const std::string& name, const PropertyType& value,
James Feista465ccc2019-02-08 12:51:01 -0800202 sdbusplus::asio::dbus_interface* iface,
203 nlohmann::json& systemConfiguration,
204 const std::string& jsonPointerString,
James Feistbb43d022018-06-12 15:44:33 -0700205 sdbusplus::asio::PropertyPermission permission)
206{
207 if (permission == sdbusplus::asio::PropertyPermission::readOnly)
208 {
Ed Tanous3013fb42022-07-09 08:27:06 -0700209 iface->register_property(name, value);
James Feistbb43d022018-06-12 15:44:33 -0700210 return;
211 }
James Feist68500ff2018-08-08 15:40:42 -0700212 iface->register_property(
Ed Tanous3013fb42022-07-09 08:27:06 -0700213 name, value,
James Feist68500ff2018-08-08 15:40:42 -0700214 [&systemConfiguration,
215 jsonPointerString{std::string(jsonPointerString)}](
James Feista465ccc2019-02-08 12:51:01 -0800216 const PropertyType& newVal, PropertyType& val) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400217 val = newVal;
218 if (!setJsonFromPointer(jsonPointerString, val,
219 systemConfiguration))
220 {
221 std::cerr << "error setting json field\n";
222 return -1;
223 }
224 if (!writeJsonFiles(systemConfiguration))
225 {
226 std::cerr << "error setting json file\n";
227 return -1;
228 }
229 return 1;
230 });
James Feistc6248a52018-08-14 10:09:45 -0700231}
232
233void createDeleteObjectMethod(
James Feista465ccc2019-02-08 12:51:01 -0800234 const std::string& jsonPointerPath,
235 const std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
236 sdbusplus::asio::object_server& objServer,
237 nlohmann::json& systemConfiguration)
James Feistc6248a52018-08-14 10:09:45 -0700238{
239 std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface;
Patrick Williamsb7077432024-08-16 15:22:21 -0400240 iface->register_method(
241 "Delete", [&objServer, &systemConfiguration, interface,
242 jsonPointerPath{std::string(jsonPointerPath)}]() {
243 std::shared_ptr<sdbusplus::asio::dbus_interface> dbusInterface =
244 interface.lock();
245 if (!dbusInterface)
246 {
247 // this technically can't happen as the pointer is pointing to
248 // us
249 throw DBusInternalError();
250 }
251 nlohmann::json::json_pointer ptr(jsonPointerPath);
252 systemConfiguration[ptr] = nullptr;
James Feistc6248a52018-08-14 10:09:45 -0700253
Patrick Williamsb7077432024-08-16 15:22:21 -0400254 // todo(james): dig through sdbusplus to find out why we can't
255 // delete it in a method call
256 boost::asio::post(io, [&objServer, dbusInterface]() mutable {
257 objServer.remove_interface(dbusInterface);
258 });
259
260 if (!writeJsonFiles(systemConfiguration))
261 {
262 std::cerr << "error setting json file\n";
263 throw DBusInternalError();
264 }
James Feist68500ff2018-08-08 15:40:42 -0700265 });
James Feistbb43d022018-06-12 15:44:33 -0700266}
267
James Feist1b2e2242018-01-30 13:45:19 -0800268// adds simple json types to interface's properties
James Feistbb43d022018-06-12 15:44:33 -0700269void populateInterfaceFromJson(
James Feista465ccc2019-02-08 12:51:01 -0800270 nlohmann::json& systemConfiguration, const std::string& jsonPointerPath,
271 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
272 nlohmann::json& dict, sdbusplus::asio::object_server& objServer,
James Feistbb43d022018-06-12 15:44:33 -0700273 sdbusplus::asio::PropertyPermission permission =
274 sdbusplus::asio::PropertyPermission::readOnly)
James Feist1b2e2242018-01-30 13:45:19 -0800275{
Patrick Williams2594d362022-09-28 06:46:24 -0500276 for (const auto& [key, value] : dict.items())
James Feist1b2e2242018-01-30 13:45:19 -0800277 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030278 auto type = value.type();
James Feist8f2710a2018-05-09 17:18:55 -0700279 bool array = false;
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030280 if (value.type() == nlohmann::json::value_t::array)
James Feist8f2710a2018-05-09 17:18:55 -0700281 {
282 array = true;
Ed Tanous3013fb42022-07-09 08:27:06 -0700283 if (value.empty())
James Feist8f2710a2018-05-09 17:18:55 -0700284 {
285 continue;
286 }
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030287 type = value[0].type();
James Feist8f2710a2018-05-09 17:18:55 -0700288 bool isLegal = true;
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030289 for (const auto& arrayItem : value)
James Feist8f2710a2018-05-09 17:18:55 -0700290 {
291 if (arrayItem.type() != type)
292 {
293 isLegal = false;
294 break;
295 }
296 }
297 if (!isLegal)
298 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030299 std::cerr << "dbus format error" << value << "\n";
James Feist8f2710a2018-05-09 17:18:55 -0700300 continue;
301 }
James Feista218ddb2019-04-11 14:01:31 -0700302 }
303 if (type == nlohmann::json::value_t::object)
304 {
305 continue; // handled elsewhere
James Feist8f2710a2018-05-09 17:18:55 -0700306 }
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030307
308 std::string path = jsonPointerPath;
309 path.append("/").append(key);
James Feistbb43d022018-06-12 15:44:33 -0700310 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
311 {
312 // all setable numbers are doubles as it is difficult to always
313 // create a configuration file with all whole numbers as decimals
314 // i.e. 1.0
James Feistebcc26b2019-03-22 12:30:43 -0700315 if (array)
316 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030317 if (value[0].is_number())
James Feistebcc26b2019-03-22 12:30:43 -0700318 {
319 type = nlohmann::json::value_t::number_float;
320 }
321 }
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030322 else if (value.is_number())
James Feistbb43d022018-06-12 15:44:33 -0700323 {
324 type = nlohmann::json::value_t::number_float;
325 }
326 }
327
James Feist8f2710a2018-05-09 17:18:55 -0700328 switch (type)
James Feist1b2e2242018-01-30 13:45:19 -0800329 {
James Feist9eb0b582018-04-27 12:15:46 -0700330 case (nlohmann::json::value_t::boolean):
331 {
James Feist8f2710a2018-05-09 17:18:55 -0700332 if (array)
333 {
334 // todo: array of bool isn't detected correctly by
335 // sdbusplus, change it to numbers
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030336 addArrayToDbus<uint64_t>(key, value, iface.get(),
337 permission, systemConfiguration,
338 path);
James Feist8f2710a2018-05-09 17:18:55 -0700339 }
James Feistbb43d022018-06-12 15:44:33 -0700340
James Feist97a63f12018-05-17 13:50:57 -0700341 else
342 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030343 addProperty(key, value.get<bool>(), iface.get(),
344 systemConfiguration, path, permission);
James Feist97a63f12018-05-17 13:50:57 -0700345 }
James Feist9eb0b582018-04-27 12:15:46 -0700346 break;
347 }
348 case (nlohmann::json::value_t::number_integer):
349 {
James Feist8f2710a2018-05-09 17:18:55 -0700350 if (array)
351 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030352 addArrayToDbus<int64_t>(key, value, iface.get(), permission,
Andrew Jeffery029ee282022-03-25 13:11:36 +1030353 systemConfiguration, path);
James Feist97a63f12018-05-17 13:50:57 -0700354 }
355 else
356 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030357 addProperty(key, value.get<int64_t>(), iface.get(),
358 systemConfiguration, path,
James Feistbb43d022018-06-12 15:44:33 -0700359 sdbusplus::asio::PropertyPermission::readOnly);
James Feist97a63f12018-05-17 13:50:57 -0700360 }
James Feist9eb0b582018-04-27 12:15:46 -0700361 break;
362 }
363 case (nlohmann::json::value_t::number_unsigned):
364 {
James Feist8f2710a2018-05-09 17:18:55 -0700365 if (array)
366 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030367 addArrayToDbus<uint64_t>(key, value, iface.get(),
368 permission, systemConfiguration,
369 path);
James Feist97a63f12018-05-17 13:50:57 -0700370 }
371 else
372 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030373 addProperty(key, value.get<uint64_t>(), iface.get(),
Andrew Jeffery029ee282022-03-25 13:11:36 +1030374 systemConfiguration, path,
James Feistbb43d022018-06-12 15:44:33 -0700375 sdbusplus::asio::PropertyPermission::readOnly);
James Feist97a63f12018-05-17 13:50:57 -0700376 }
James Feist9eb0b582018-04-27 12:15:46 -0700377 break;
378 }
379 case (nlohmann::json::value_t::number_float):
380 {
James Feist8f2710a2018-05-09 17:18:55 -0700381 if (array)
382 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030383 addArrayToDbus<double>(key, value, iface.get(), permission,
Andrew Jeffery029ee282022-03-25 13:11:36 +1030384 systemConfiguration, path);
James Feist8f2710a2018-05-09 17:18:55 -0700385 }
James Feistbb43d022018-06-12 15:44:33 -0700386
James Feist97a63f12018-05-17 13:50:57 -0700387 else
388 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030389 addProperty(key, value.get<double>(), iface.get(),
390 systemConfiguration, path, permission);
James Feist97a63f12018-05-17 13:50:57 -0700391 }
James Feist9eb0b582018-04-27 12:15:46 -0700392 break;
393 }
394 case (nlohmann::json::value_t::string):
395 {
James Feist8f2710a2018-05-09 17:18:55 -0700396 if (array)
397 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030398 addArrayToDbus<std::string>(key, value, iface.get(),
399 permission, systemConfiguration,
400 path);
James Feist97a63f12018-05-17 13:50:57 -0700401 }
402 else
403 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030404 addProperty(key, value.get<std::string>(), iface.get(),
405 systemConfiguration, path, permission);
James Feist97a63f12018-05-17 13:50:57 -0700406 }
James Feist9eb0b582018-04-27 12:15:46 -0700407 break;
408 }
James Feist0eb40352019-04-09 14:44:04 -0700409 default:
410 {
James Feista218ddb2019-04-11 14:01:31 -0700411 std::cerr << "Unexpected json type in system configuration "
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030412 << key << ": " << value.type_name() << "\n";
James Feist0eb40352019-04-09 14:44:04 -0700413 break;
414 }
James Feist1b2e2242018-01-30 13:45:19 -0800415 }
416 }
James Feistc6248a52018-08-14 10:09:45 -0700417 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
418 {
419 createDeleteObjectMethod(jsonPointerPath, iface, objServer,
420 systemConfiguration);
421 }
John Edward Broadbentd97c6312023-10-26 20:32:07 +0000422 tryIfaceInitialize(iface);
James Feist1b2e2242018-01-30 13:45:19 -0800423}
424
James Feista465ccc2019-02-08 12:51:01 -0800425sdbusplus::asio::PropertyPermission getPermission(const std::string& interface)
James Feistc6248a52018-08-14 10:09:45 -0700426{
427 return std::find(settableInterfaces.begin(), settableInterfaces.end(),
428 interface) != settableInterfaces.end()
429 ? sdbusplus::asio::PropertyPermission::readWrite
430 : sdbusplus::asio::PropertyPermission::readOnly;
431}
432
Patrick Williamsb7077432024-08-16 15:22:21 -0400433void createAddObjectMethod(
434 const std::string& jsonPointerPath, const std::string& path,
435 nlohmann::json& systemConfiguration,
436 sdbusplus::asio::object_server& objServer, const std::string& board)
James Feist68500ff2018-08-08 15:40:42 -0700437{
James Feistd58879a2019-09-11 11:26:07 -0700438 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = createInterface(
439 objServer, path, "xyz.openbmc_project.AddObject", board);
James Feist68500ff2018-08-08 15:40:42 -0700440
441 iface->register_method(
442 "AddObject",
443 [&systemConfiguration, &objServer,
James Feistd58879a2019-09-11 11:26:07 -0700444 jsonPointerPath{std::string(jsonPointerPath)}, path{std::string(path)},
445 board](const boost::container::flat_map<std::string, JsonVariantType>&
446 data) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400447 nlohmann::json::json_pointer ptr(jsonPointerPath);
448 nlohmann::json& base = systemConfiguration[ptr];
449 auto findExposes = base.find("Exposes");
James Feist68500ff2018-08-08 15:40:42 -0700450
Patrick Williamsb7077432024-08-16 15:22:21 -0400451 if (findExposes == base.end())
James Feist68500ff2018-08-08 15:40:42 -0700452 {
Patrick Williamsb7077432024-08-16 15:22:21 -0400453 throw std::invalid_argument("Entity must have children.");
James Feist68500ff2018-08-08 15:40:42 -0700454 }
455
Patrick Williamsb7077432024-08-16 15:22:21 -0400456 // this will throw invalid-argument to sdbusplus if invalid json
457 nlohmann::json newData{};
458 for (const auto& item : data)
459 {
460 nlohmann::json& newJson = newData[item.first];
461 std::visit(
462 [&newJson](auto&& val) {
463 newJson = std::forward<decltype(val)>(val);
464 },
465 item.second);
466 }
467
468 auto findName = newData.find("Name");
469 auto findType = newData.find("Type");
470 if (findName == newData.end() || findType == newData.end())
471 {
472 throw std::invalid_argument("AddObject missing Name or Type");
473 }
474 const std::string* type = findType->get_ptr<const std::string*>();
475 const std::string* name = findName->get_ptr<const std::string*>();
476 if (type == nullptr || name == nullptr)
477 {
478 throw std::invalid_argument("Type and Name must be a string.");
479 }
480
481 bool foundNull = false;
482 size_t lastIndex = 0;
483 // we add in the "exposes"
484 for (const auto& expose : *findExposes)
485 {
486 if (expose.is_null())
487 {
488 foundNull = true;
489 continue;
490 }
491
492 if (expose["Name"] == *name && expose["Type"] == *type)
493 {
494 throw std::invalid_argument(
495 "Field already in JSON, not adding");
496 }
497
498 if (foundNull)
499 {
500 continue;
501 }
502
503 lastIndex++;
504 }
505
506 std::ifstream schemaFile(std::string(schemaDirectory) + "/" +
507 boost::to_lower_copy(*type) + ".json");
508 // todo(james) we might want to also make a list of 'can add'
509 // interfaces but for now I think the assumption if there is a
510 // schema avaliable that it is allowed to update is fine
511 if (!schemaFile.good())
James Feist68500ff2018-08-08 15:40:42 -0700512 {
513 throw std::invalid_argument(
Patrick Williamsb7077432024-08-16 15:22:21 -0400514 "No schema avaliable, cannot validate.");
James Feist68500ff2018-08-08 15:40:42 -0700515 }
Patrick Williamsb7077432024-08-16 15:22:21 -0400516 nlohmann::json schema =
517 nlohmann::json::parse(schemaFile, nullptr, false, true);
518 if (schema.is_discarded())
519 {
520 std::cerr << "Schema not legal" << *type << ".json\n";
521 throw DBusInternalError();
522 }
523 if (!validateJson(schema, newData))
524 {
525 throw std::invalid_argument("Data does not match schema");
526 }
James Feist02d2b932020-02-06 16:28:48 -0800527 if (foundNull)
528 {
Patrick Williamsb7077432024-08-16 15:22:21 -0400529 findExposes->at(lastIndex) = newData;
James Feist02d2b932020-02-06 16:28:48 -0800530 }
Patrick Williamsb7077432024-08-16 15:22:21 -0400531 else
532 {
533 findExposes->push_back(newData);
534 }
535 if (!writeJsonFiles(systemConfiguration))
536 {
537 std::cerr << "Error writing json files\n";
538 throw DBusInternalError();
539 }
540 std::string dbusName = *name;
James Feist68500ff2018-08-08 15:40:42 -0700541
Patrick Williamsb7077432024-08-16 15:22:21 -0400542 std::regex_replace(dbusName.begin(), dbusName.begin(),
543 dbusName.end(), illegalDbusMemberRegex, "_");
James Feistd58879a2019-09-11 11:26:07 -0700544
Patrick Williamsb7077432024-08-16 15:22:21 -0400545 std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
546 createInterface(objServer, path + "/" + dbusName,
547 "xyz.openbmc_project.Configuration." + *type,
548 board, true);
549 // permission is read-write, as since we just created it, must be
550 // runtime modifiable
551 populateInterfaceFromJson(
552 systemConfiguration,
553 jsonPointerPath + "/Exposes/" + std::to_string(lastIndex),
554 interface, newData, objServer,
555 sdbusplus::asio::PropertyPermission::readWrite);
556 });
John Edward Broadbentd97c6312023-10-26 20:32:07 +0000557 tryIfaceInitialize(iface);
James Feist68500ff2018-08-08 15:40:42 -0700558}
559
James Feista465ccc2019-02-08 12:51:01 -0800560void postToDbus(const nlohmann::json& newConfiguration,
561 nlohmann::json& systemConfiguration,
562 sdbusplus::asio::object_server& objServer)
James Feist75fdeeb2018-02-20 14:26:16 -0800563
James Feist1b2e2242018-01-30 13:45:19 -0800564{
Matt Spinler6eb60972023-08-14 16:36:20 -0500565 std::map<std::string, std::string> newBoards; // path -> name
Benjamin Fairca2eb042022-09-13 06:40:42 +0000566
James Feist97a63f12018-05-17 13:50:57 -0700567 // iterate through boards
Patrick Williams2594d362022-09-28 06:46:24 -0500568 for (const auto& [boardId, boardConfig] : newConfiguration.items())
James Feist1b2e2242018-01-30 13:45:19 -0800569 {
Matt Spinler3d1909a2023-08-10 16:39:44 -0500570 std::string boardName = boardConfig["Name"];
571 std::string boardNameOrig = boardConfig["Name"];
Andrew Jeffery13132df2022-03-25 13:29:41 +1030572 std::string jsonPointerPath = "/" + boardId;
James Feist97a63f12018-05-17 13:50:57 -0700573 // loop through newConfiguration, but use values from system
574 // configuration to be able to modify via dbus later
Andrew Jeffery13132df2022-03-25 13:29:41 +1030575 auto boardValues = systemConfiguration[boardId];
James Feistd63d18a2018-07-19 15:23:45 -0700576 auto findBoardType = boardValues.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -0800577 std::string boardType;
578 if (findBoardType != boardValues.end() &&
579 findBoardType->type() == nlohmann::json::value_t::string)
580 {
581 boardType = findBoardType->get<std::string>();
582 std::regex_replace(boardType.begin(), boardType.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800583 boardType.end(), illegalDbusMemberRegex, "_");
James Feist1b2e2242018-01-30 13:45:19 -0800584 }
585 else
586 {
Matt Spinler3d1909a2023-08-10 16:39:44 -0500587 std::cerr << "Unable to find type for " << boardName
James Feist1b2e2242018-01-30 13:45:19 -0800588 << " reverting to Chassis.\n";
589 boardType = "Chassis";
590 }
James Feist11be6672018-04-06 14:05:32 -0700591 std::string boardtypeLower = boost::algorithm::to_lower_copy(boardType);
James Feist1b2e2242018-01-30 13:45:19 -0800592
Matt Spinler3d1909a2023-08-10 16:39:44 -0500593 std::regex_replace(boardName.begin(), boardName.begin(),
594 boardName.end(), illegalDbusMemberRegex, "_");
595 std::string boardPath = "/xyz/openbmc_project/inventory/system/";
596 boardPath += boardtypeLower;
597 boardPath += "/";
598 boardPath += boardName;
James Feist1b2e2242018-01-30 13:45:19 -0800599
James Feistd58879a2019-09-11 11:26:07 -0700600 std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
Matt Spinler3d1909a2023-08-10 16:39:44 -0500601 createInterface(objServer, boardPath,
602 "xyz.openbmc_project.Inventory.Item", boardName);
James Feist68500ff2018-08-08 15:40:42 -0700603
James Feistd58879a2019-09-11 11:26:07 -0700604 std::shared_ptr<sdbusplus::asio::dbus_interface> boardIface =
Matt Spinler3d1909a2023-08-10 16:39:44 -0500605 createInterface(objServer, boardPath,
James Feistd58879a2019-09-11 11:26:07 -0700606 "xyz.openbmc_project.Inventory.Item." + boardType,
Matt Spinler3d1909a2023-08-10 16:39:44 -0500607 boardNameOrig);
James Feist11be6672018-04-06 14:05:32 -0700608
Matt Spinler3d1909a2023-08-10 16:39:44 -0500609 createAddObjectMethod(jsonPointerPath, boardPath, systemConfiguration,
610 objServer, boardNameOrig);
James Feist68500ff2018-08-08 15:40:42 -0700611
James Feist97a63f12018-05-17 13:50:57 -0700612 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
James Feistc6248a52018-08-14 10:09:45 -0700613 boardIface, boardValues, objServer);
James Feist97a63f12018-05-17 13:50:57 -0700614 jsonPointerPath += "/";
615 // iterate through board properties
Patrick Williams2594d362022-09-28 06:46:24 -0500616 for (const auto& [propName, propValue] : boardValues.items())
James Feist11be6672018-04-06 14:05:32 -0700617 {
Andrew Jefferya96950d2022-03-25 13:32:46 +1030618 if (propValue.type() == nlohmann::json::value_t::object)
James Feist11be6672018-04-06 14:05:32 -0700619 {
James Feistd58879a2019-09-11 11:26:07 -0700620 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
Matt Spinler3d1909a2023-08-10 16:39:44 -0500621 createInterface(objServer, boardPath, propName,
622 boardNameOrig);
James Feistd58879a2019-09-11 11:26:07 -0700623
James Feistc6248a52018-08-14 10:09:45 -0700624 populateInterfaceFromJson(systemConfiguration,
Andrew Jefferya96950d2022-03-25 13:32:46 +1030625 jsonPointerPath + propName, iface,
626 propValue, objServer);
James Feist11be6672018-04-06 14:05:32 -0700627 }
628 }
James Feist97a63f12018-05-17 13:50:57 -0700629
James Feist1e3e6982018-08-03 16:09:28 -0700630 auto exposes = boardValues.find("Exposes");
James Feist1b2e2242018-01-30 13:45:19 -0800631 if (exposes == boardValues.end())
632 {
633 continue;
634 }
James Feist97a63f12018-05-17 13:50:57 -0700635 // iterate through exposes
James Feist1e3e6982018-08-03 16:09:28 -0700636 jsonPointerPath += "Exposes/";
James Feist97a63f12018-05-17 13:50:57 -0700637
638 // store the board level pointer so we can modify it on the way down
639 std::string jsonPointerPathBoard = jsonPointerPath;
640 size_t exposesIndex = -1;
James Feista465ccc2019-02-08 12:51:01 -0800641 for (auto& item : *exposes)
James Feist1b2e2242018-01-30 13:45:19 -0800642 {
James Feist97a63f12018-05-17 13:50:57 -0700643 exposesIndex++;
644 jsonPointerPath = jsonPointerPathBoard;
645 jsonPointerPath += std::to_string(exposesIndex);
646
James Feistd63d18a2018-07-19 15:23:45 -0700647 auto findName = item.find("Name");
James Feist1b2e2242018-01-30 13:45:19 -0800648 if (findName == item.end())
649 {
650 std::cerr << "cannot find name in field " << item << "\n";
651 continue;
652 }
James Feist1e3e6982018-08-03 16:09:28 -0700653 auto findStatus = item.find("Status");
James Feist1b2e2242018-01-30 13:45:19 -0800654 // if status is not found it is assumed to be status = 'okay'
655 if (findStatus != item.end())
656 {
657 if (*findStatus == "disabled")
658 {
659 continue;
660 }
661 }
James Feistd63d18a2018-07-19 15:23:45 -0700662 auto findType = item.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -0800663 std::string itemType;
664 if (findType != item.end())
665 {
666 itemType = findType->get<std::string>();
667 std::regex_replace(itemType.begin(), itemType.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800668 itemType.end(), illegalDbusPathRegex, "_");
James Feist1b2e2242018-01-30 13:45:19 -0800669 }
670 else
671 {
672 itemType = "unknown";
673 }
674 std::string itemName = findName->get<std::string>();
675 std::regex_replace(itemName.begin(), itemName.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800676 itemName.end(), illegalDbusMemberRegex, "_");
Matt Spinler3d1909a2023-08-10 16:39:44 -0500677 std::string ifacePath = boardPath;
Ed Tanous07d467b2021-02-23 14:48:37 -0800678 ifacePath += "/";
679 ifacePath += itemName;
James Feistc6248a52018-08-14 10:09:45 -0700680
Sui Chen74ebe592022-09-13 10:22:03 -0700681 if (itemType == "BMC")
682 {
683 std::shared_ptr<sdbusplus::asio::dbus_interface> bmcIface =
684 createInterface(objServer, ifacePath,
685 "xyz.openbmc_project.Inventory.Item.Bmc",
Matt Spinler3d1909a2023-08-10 16:39:44 -0500686 boardNameOrig);
Sui Chen74ebe592022-09-13 10:22:03 -0700687 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
688 bmcIface, item, objServer,
689 getPermission(itemType));
690 }
Edward Leeeb587b42023-03-08 18:59:04 +0000691 else if (itemType == "System")
692 {
693 std::shared_ptr<sdbusplus::asio::dbus_interface> systemIface =
694 createInterface(objServer, ifacePath,
695 "xyz.openbmc_project.Inventory.Item.System",
Matt Spinler3d1909a2023-08-10 16:39:44 -0500696 boardNameOrig);
Edward Leeeb587b42023-03-08 18:59:04 +0000697 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
698 systemIface, item, objServer,
699 getPermission(itemType));
700 }
Sui Chen74ebe592022-09-13 10:22:03 -0700701
Patrick Williams2594d362022-09-28 06:46:24 -0500702 for (const auto& [name, config] : item.items())
James Feist1b2e2242018-01-30 13:45:19 -0800703 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030704 jsonPointerPath = jsonPointerPathBoard;
705 jsonPointerPath.append(std::to_string(exposesIndex))
706 .append("/")
707 .append(name);
708 if (config.type() == nlohmann::json::value_t::object)
James Feist1b2e2242018-01-30 13:45:19 -0800709 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030710 std::string ifaceName =
711 "xyz.openbmc_project.Configuration.";
712 ifaceName.append(itemType).append(".").append(name);
James Feist97a63f12018-05-17 13:50:57 -0700713
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030714 std::shared_ptr<sdbusplus::asio::dbus_interface>
715 objectIface = createInterface(objServer, ifacePath,
Matt Spinler3d1909a2023-08-10 16:39:44 -0500716 ifaceName, boardNameOrig);
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030717
718 populateInterfaceFromJson(
719 systemConfiguration, jsonPointerPath, objectIface,
720 config, objServer, getPermission(name));
James Feist1b2e2242018-01-30 13:45:19 -0800721 }
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030722 else if (config.type() == nlohmann::json::value_t::array)
James Feist1b2e2242018-01-30 13:45:19 -0800723 {
724 size_t index = 0;
Ed Tanous3013fb42022-07-09 08:27:06 -0700725 if (config.empty())
James Feist1b2e2242018-01-30 13:45:19 -0800726 {
James Feist8f2710a2018-05-09 17:18:55 -0700727 continue;
728 }
729 bool isLegal = true;
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030730 auto type = config[0].type();
James Feist8f2710a2018-05-09 17:18:55 -0700731 if (type != nlohmann::json::value_t::object)
732 {
733 continue;
734 }
735
736 // verify legal json
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030737 for (const auto& arrayItem : config)
James Feist8f2710a2018-05-09 17:18:55 -0700738 {
739 if (arrayItem.type() != type)
James Feist1b2e2242018-01-30 13:45:19 -0800740 {
James Feist8f2710a2018-05-09 17:18:55 -0700741 isLegal = false;
James Feist1b2e2242018-01-30 13:45:19 -0800742 break;
743 }
James Feist8f2710a2018-05-09 17:18:55 -0700744 }
745 if (!isLegal)
746 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030747 std::cerr << "dbus format error" << config << "\n";
James Feist8f2710a2018-05-09 17:18:55 -0700748 break;
749 }
750
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030751 for (auto& arrayItem : config)
James Feist8f2710a2018-05-09 17:18:55 -0700752 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030753 std::string ifaceName =
754 "xyz.openbmc_project.Configuration.";
755 ifaceName.append(itemType).append(".").append(name);
756 ifaceName.append(std::to_string(index));
James Feist97a63f12018-05-17 13:50:57 -0700757
James Feistd58879a2019-09-11 11:26:07 -0700758 std::shared_ptr<sdbusplus::asio::dbus_interface>
759 objectIface = createInterface(
Matt Spinler3d1909a2023-08-10 16:39:44 -0500760 objServer, ifacePath, ifaceName, boardNameOrig);
James Feistd58879a2019-09-11 11:26:07 -0700761
James Feistc6248a52018-08-14 10:09:45 -0700762 populateInterfaceFromJson(
763 systemConfiguration,
764 jsonPointerPath + "/" + std::to_string(index),
765 objectIface, arrayItem, objServer,
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030766 getPermission(name));
James Feistbb43d022018-06-12 15:44:33 -0700767 index++;
James Feist1b2e2242018-01-30 13:45:19 -0800768 }
769 }
770 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000771
George Liu5c1a61a2024-10-24 18:02:29 +0800772 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface =
773 createInterface(objServer, ifacePath,
774 "xyz.openbmc_project.Configuration." + itemType,
775 boardNameOrig);
776
777 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
778 itemIface, item, objServer,
779 getPermission(itemType));
780
Matt Spinler6eb60972023-08-14 16:36:20 -0500781 topology.addBoard(boardPath, boardType, boardNameOrig, item);
James Feist1b2e2242018-01-30 13:45:19 -0800782 }
Matt Spinler6eb60972023-08-14 16:36:20 -0500783
784 newBoards.emplace(boardPath, boardNameOrig);
James Feist1b2e2242018-01-30 13:45:19 -0800785 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000786
Matt Spinler6eb60972023-08-14 16:36:20 -0500787 for (const auto& [assocPath, assocPropValue] :
788 topology.getAssocs(newBoards))
Benjamin Fairca2eb042022-09-13 06:40:42 +0000789 {
Matt Spinler6eb60972023-08-14 16:36:20 -0500790 auto findBoard = newBoards.find(assocPath);
791 if (findBoard == newBoards.end())
792 {
793 continue;
794 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000795
Matt Spinler6eb60972023-08-14 16:36:20 -0500796 auto ifacePtr = createInterface(
797 objServer, assocPath, "xyz.openbmc_project.Association.Definitions",
798 findBoard->second);
799
800 ifacePtr->register_property("Associations", assocPropValue);
John Edward Broadbentd97c6312023-10-26 20:32:07 +0000801 tryIfaceInitialize(ifacePtr);
Benjamin Fairca2eb042022-09-13 06:40:42 +0000802 }
James Feist1b2e2242018-01-30 13:45:19 -0800803}
804
James Feist8f2710a2018-05-09 17:18:55 -0700805// reads json files out of the filesystem
Andrew Jefferyf3311792022-03-29 22:09:00 +1030806bool loadConfigurations(std::list<nlohmann::json>& configurations)
James Feist3cb5fec2018-01-23 14:41:51 -0800807{
808 // find configuration files
Ed Tanous072e25d2018-12-16 21:45:20 -0800809 std::vector<std::filesystem::path> jsonPaths;
Andrew Jefferya9c58922021-06-01 09:28:59 +0930810 if (!findFiles(
811 std::vector<std::filesystem::path>{configurationDirectory,
812 hostConfigurationDirectory},
813 R"(.*\.json)", jsonPaths))
James Feist3cb5fec2018-01-23 14:41:51 -0800814 {
815 std::cerr << "Unable to find any configuration files in "
James Feistb4383f42018-08-06 16:54:10 -0700816 << configurationDirectory << "\n";
James Feist75fdeeb2018-02-20 14:26:16 -0800817 return false;
James Feist3cb5fec2018-01-23 14:41:51 -0800818 }
James Feistb4383f42018-08-06 16:54:10 -0700819
Patrick Williamsb7077432024-08-16 15:22:21 -0400820 std::ifstream schemaStream(
821 std::string(schemaDirectory) + "/" + globalSchema);
James Feistb4383f42018-08-06 16:54:10 -0700822 if (!schemaStream.good())
823 {
824 std::cerr
825 << "Cannot open schema file, cannot validate JSON, exiting\n\n";
826 std::exit(EXIT_FAILURE);
Ed Tanous072e25d2018-12-16 21:45:20 -0800827 return false;
James Feistb4383f42018-08-06 16:54:10 -0700828 }
Patrick Williamsb7077432024-08-16 15:22:21 -0400829 nlohmann::json schema =
830 nlohmann::json::parse(schemaStream, nullptr, false, true);
James Feistb4383f42018-08-06 16:54:10 -0700831 if (schema.is_discarded())
832 {
833 std::cerr
834 << "Illegal schema file detected, cannot validate JSON, exiting\n";
835 std::exit(EXIT_FAILURE);
Ed Tanous072e25d2018-12-16 21:45:20 -0800836 return false;
James Feistb4383f42018-08-06 16:54:10 -0700837 }
838
James Feista465ccc2019-02-08 12:51:01 -0800839 for (auto& jsonPath : jsonPaths)
James Feist3cb5fec2018-01-23 14:41:51 -0800840 {
841 std::ifstream jsonStream(jsonPath.c_str());
842 if (!jsonStream.good())
843 {
844 std::cerr << "unable to open " << jsonPath.string() << "\n";
845 continue;
846 }
Potin Lai0f3a4d92023-12-05 00:13:55 +0800847 auto data = nlohmann::json::parse(jsonStream, nullptr, false, true);
James Feist3cb5fec2018-01-23 14:41:51 -0800848 if (data.is_discarded())
849 {
850 std::cerr << "syntax error in " << jsonPath.string() << "\n";
851 continue;
852 }
James Feist8da99192019-01-24 08:20:16 -0800853 /*
854 * todo(james): reenable this once less things are in flight
855 *
James Feistb4383f42018-08-06 16:54:10 -0700856 if (!validateJson(schema, data))
857 {
858 std::cerr << "Error validating " << jsonPath.string() << "\n";
859 continue;
860 }
James Feist8da99192019-01-24 08:20:16 -0800861 */
James Feistb4383f42018-08-06 16:54:10 -0700862
James Feist3cb5fec2018-01-23 14:41:51 -0800863 if (data.type() == nlohmann::json::value_t::array)
864 {
James Feista465ccc2019-02-08 12:51:01 -0800865 for (auto& d : data)
James Feist3cb5fec2018-01-23 14:41:51 -0800866 {
867 configurations.emplace_back(d);
868 }
869 }
870 else
871 {
872 configurations.emplace_back(data);
873 }
874 }
Ed Tanous072e25d2018-12-16 21:45:20 -0800875 return true;
James Feist75fdeeb2018-02-20 14:26:16 -0800876}
James Feist3cb5fec2018-01-23 14:41:51 -0800877
Andrew Jeffery55192932022-03-24 12:29:27 +1030878static bool deviceRequiresPowerOn(const nlohmann::json& entity)
879{
880 auto powerState = entity.find("PowerState");
Andrew Jefferyb6209442022-03-24 12:36:20 +1030881 if (powerState == entity.end())
Andrew Jeffery55192932022-03-24 12:29:27 +1030882 {
Andrew Jefferyb6209442022-03-24 12:36:20 +1030883 return false;
Andrew Jeffery55192932022-03-24 12:29:27 +1030884 }
885
Ed Tanous3013fb42022-07-09 08:27:06 -0700886 const auto* ptr = powerState->get_ptr<const std::string*>();
887 if (ptr == nullptr)
Andrew Jefferyb6209442022-03-24 12:36:20 +1030888 {
889 return false;
890 }
891
892 return *ptr == "On" || *ptr == "BiosPost";
Andrew Jeffery55192932022-03-24 12:29:27 +1030893}
894
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030895static void pruneDevice(const nlohmann::json& systemConfiguration,
896 const bool powerOff, const bool scannedPowerOff,
897 const std::string& name, const nlohmann::json& device)
898{
899 if (systemConfiguration.contains(name))
900 {
901 return;
902 }
903
Andrew Jeffery4db38bc2022-03-24 13:42:41 +1030904 if (deviceRequiresPowerOn(device) && (powerOff || scannedPowerOff))
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030905 {
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030906 return;
907 }
908
909 logDeviceRemoved(device);
910}
911
James Feistb1728ca2020-04-30 15:40:55 -0700912void startRemovedTimer(boost::asio::steady_timer& timer,
James Feist1df06a42019-04-11 14:23:04 -0700913 nlohmann::json& systemConfiguration)
914{
915 static bool scannedPowerOff = false;
916 static bool scannedPowerOn = false;
917
James Feistfb00f392019-06-25 14:16:48 -0700918 if (systemConfiguration.empty() || lastJson.empty())
919 {
920 return; // not ready yet
921 }
James Feist1df06a42019-04-11 14:23:04 -0700922 if (scannedPowerOn)
923 {
924 return;
925 }
926
927 if (!isPowerOn() && scannedPowerOff)
928 {
929 return;
930 }
931
James Feistb1728ca2020-04-30 15:40:55 -0700932 timer.expires_after(std::chrono::seconds(10));
Andrew Jeffery27a1cfb2022-03-24 12:31:53 +1030933 timer.async_wait(
934 [&systemConfiguration](const boost::system::error_code& ec) {
Patrick Williamsb7077432024-08-16 15:22:21 -0400935 if (ec == boost::asio::error::operation_aborted)
936 {
937 return;
938 }
Andrew Jeffery27a1cfb2022-03-24 12:31:53 +1030939
Patrick Williamsb7077432024-08-16 15:22:21 -0400940 bool powerOff = !isPowerOn();
941 for (const auto& [name, device] : lastJson.items())
942 {
943 pruneDevice(systemConfiguration, powerOff, scannedPowerOff,
944 name, device);
945 }
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030946
Patrick Williamsb7077432024-08-16 15:22:21 -0400947 scannedPowerOff = true;
948 if (!powerOff)
949 {
950 scannedPowerOn = true;
951 }
952 });
James Feist1df06a42019-04-11 14:23:04 -0700953}
954
Andrew Jeffery2f750d22022-03-24 14:32:57 +1030955static std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>&
956 getDeviceInterfaces(const nlohmann::json& device)
957{
958 return inventory[device["Name"].get<std::string>()];
959}
960
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030961static void pruneConfiguration(nlohmann::json& systemConfiguration,
962 sdbusplus::asio::object_server& objServer,
963 bool powerOff, const std::string& name,
964 const nlohmann::json& device)
965{
966 if (powerOff && deviceRequiresPowerOn(device))
967 {
968 // power not on yet, don't know if it's there or not
969 return;
970 }
971
972 auto& ifaces = getDeviceInterfaces(device);
973 for (auto& iface : ifaces)
974 {
975 auto sharedPtr = iface.lock();
976 if (!!sharedPtr)
977 {
978 objServer.remove_interface(sharedPtr);
979 }
980 }
981
982 ifaces.clear();
983 systemConfiguration.erase(name);
Matt Spinler6eb60972023-08-14 16:36:20 -0500984 topology.remove(device["Name"].get<std::string>());
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030985 logDeviceRemoved(device);
986}
987
Andrew Jeffery7c4cbbe2022-03-24 15:27:10 +1030988static void deriveNewConfiguration(const nlohmann::json& oldConfiguration,
989 nlohmann::json& newConfiguration)
990{
991 for (auto it = newConfiguration.begin(); it != newConfiguration.end();)
992 {
993 auto findKey = oldConfiguration.find(it.key());
994 if (findKey != oldConfiguration.end())
995 {
996 it = newConfiguration.erase(it);
997 }
998 else
999 {
1000 it++;
1001 }
1002 }
1003}
1004
Andrew Jefferye35d0ac2022-03-24 15:53:13 +10301005static void publishNewConfiguration(
1006 const size_t& instance, const size_t count,
1007 boost::asio::steady_timer& timer, nlohmann::json& systemConfiguration,
1008 // Gerrit discussion:
1009 // https://gerrit.openbmc-project.xyz/c/openbmc/entity-manager/+/52316/6
1010 //
1011 // Discord discussion:
1012 // https://discord.com/channels/775381525260664832/867820390406422538/958048437729910854
1013 //
1014 // NOLINTNEXTLINE(performance-unnecessary-value-param)
1015 const nlohmann::json newConfiguration,
1016 sdbusplus::asio::object_server& objServer)
1017{
1018 loadOverlays(newConfiguration);
1019
Ed Tanous49a888c2023-03-06 13:44:51 -08001020 boost::asio::post(io, [systemConfiguration]() {
Andrew Jefferye35d0ac2022-03-24 15:53:13 +10301021 if (!writeJsonFiles(systemConfiguration))
1022 {
1023 std::cerr << "Error writing json files\n";
1024 }
1025 });
1026
Ed Tanous49a888c2023-03-06 13:44:51 -08001027 boost::asio::post(io, [&instance, count, &timer, newConfiguration,
1028 &systemConfiguration, &objServer]() {
Andrew Jefferye35d0ac2022-03-24 15:53:13 +10301029 postToDbus(newConfiguration, systemConfiguration, objServer);
1030 if (count == instance)
1031 {
1032 startRemovedTimer(timer, systemConfiguration);
1033 }
1034 });
1035}
1036
James Feist8f2710a2018-05-09 17:18:55 -07001037// main properties changed entry
James Feist4dc617b2020-05-01 09:54:47 -07001038void propertiesChangedCallback(nlohmann::json& systemConfiguration,
1039 sdbusplus::asio::object_server& objServer)
James Feist8f2710a2018-05-09 17:18:55 -07001040{
James Feist2539ccd2020-05-01 16:15:08 -07001041 static bool inProgress = false;
James Feistb1728ca2020-04-30 15:40:55 -07001042 static boost::asio::steady_timer timer(io);
James Feist899e17f2019-09-13 11:46:29 -07001043 static size_t instance = 0;
1044 instance++;
1045 size_t count = instance;
James Feist1df06a42019-04-11 14:23:04 -07001046
Anupama B Rbf263982024-01-25 04:42:39 -06001047 timer.expires_after(std::chrono::milliseconds(500));
James Feist8f2710a2018-05-09 17:18:55 -07001048
1049 // setup an async wait as we normally get flooded with new requests
James Feist4dc617b2020-05-01 09:54:47 -07001050 timer.async_wait([&systemConfiguration, &objServer,
James Feist899e17f2019-09-13 11:46:29 -07001051 count](const boost::system::error_code& ec) {
James Feist8f2710a2018-05-09 17:18:55 -07001052 if (ec == boost::asio::error::operation_aborted)
1053 {
1054 // we were cancelled
1055 return;
1056 }
Ed Tanous07d467b2021-02-23 14:48:37 -08001057 if (ec)
James Feist8f2710a2018-05-09 17:18:55 -07001058 {
1059 std::cerr << "async wait error " << ec << "\n";
1060 return;
1061 }
1062
James Feist2539ccd2020-05-01 16:15:08 -07001063 if (inProgress)
1064 {
1065 propertiesChangedCallback(systemConfiguration, objServer);
1066 return;
1067 }
1068 inProgress = true;
1069
James Feist8f2710a2018-05-09 17:18:55 -07001070 nlohmann::json oldConfiguration = systemConfiguration;
James Feist899e17f2019-09-13 11:46:29 -07001071 auto missingConfigurations = std::make_shared<nlohmann::json>();
1072 *missingConfigurations = systemConfiguration;
1073
James Feist8f2710a2018-05-09 17:18:55 -07001074 std::list<nlohmann::json> configurations;
Andrew Jefferyf3311792022-03-29 22:09:00 +10301075 if (!loadConfigurations(configurations))
James Feist8f2710a2018-05-09 17:18:55 -07001076 {
Andrew Jefferyf3311792022-03-29 22:09:00 +10301077 std::cerr << "Could not load configurations\n";
James Feist2539ccd2020-05-01 16:15:08 -07001078 inProgress = false;
James Feist8f2710a2018-05-09 17:18:55 -07001079 return;
1080 }
1081
Christopher Meis26fbbd52025-03-26 14:55:06 +01001082 auto perfScan = std::make_shared<scan::PerformScan>(
James Feist899e17f2019-09-13 11:46:29 -07001083 systemConfiguration, *missingConfigurations, configurations,
James Feist4dc617b2020-05-01 09:54:47 -07001084 objServer,
1085 [&systemConfiguration, &objServer, count, oldConfiguration,
1086 missingConfigurations]() {
Patrick Williamsb7077432024-08-16 15:22:21 -04001087 // this is something that since ac has been applied to the bmc
1088 // we saw, and we no longer see it
1089 bool powerOff = !isPowerOn();
1090 for (const auto& [name, device] :
1091 missingConfigurations->items())
1092 {
1093 pruneConfiguration(systemConfiguration, objServer, powerOff,
1094 name, device);
1095 }
James Feist899e17f2019-09-13 11:46:29 -07001096
Patrick Williamsb7077432024-08-16 15:22:21 -04001097 nlohmann::json newConfiguration = systemConfiguration;
Andrew Jeffery7c4cbbe2022-03-24 15:27:10 +10301098
Patrick Williamsb7077432024-08-16 15:22:21 -04001099 deriveNewConfiguration(oldConfiguration, newConfiguration);
Andrew Jeffery7c4cbbe2022-03-24 15:27:10 +10301100
Patrick Williamsb7077432024-08-16 15:22:21 -04001101 for (const auto& [_, device] : newConfiguration.items())
1102 {
1103 logDeviceAdded(device);
1104 }
James Feist899e17f2019-09-13 11:46:29 -07001105
Patrick Williamsb7077432024-08-16 15:22:21 -04001106 inProgress = false;
James Feist2539ccd2020-05-01 16:15:08 -07001107
Patrick Williamsb7077432024-08-16 15:22:21 -04001108 boost::asio::post(
1109 io, std::bind_front(
1110 publishNewConfiguration, std::ref(instance), count,
1111 std::ref(timer), std::ref(systemConfiguration),
1112 newConfiguration, std::ref(objServer)));
1113 });
James Feist8f2710a2018-05-09 17:18:55 -07001114 perfScan->run();
1115 });
James Feist75fdeeb2018-02-20 14:26:16 -08001116}
1117
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001118// Extract the D-Bus interfaces to probe from the JSON config files.
1119static std::set<std::string> getProbeInterfaces()
1120{
1121 std::set<std::string> interfaces;
1122 std::list<nlohmann::json> configurations;
1123 if (!loadConfigurations(configurations))
1124 {
1125 return interfaces;
1126 }
1127
1128 for (auto it = configurations.begin(); it != configurations.end();)
1129 {
1130 auto findProbe = it->find("Probe");
1131 if (findProbe == it->end())
1132 {
1133 std::cerr << "configuration file missing probe:\n " << *it << "\n";
1134 it++;
1135 continue;
1136 }
1137
1138 nlohmann::json probeCommand;
1139 if ((*findProbe).type() != nlohmann::json::value_t::array)
1140 {
1141 probeCommand = nlohmann::json::array();
1142 probeCommand.push_back(*findProbe);
1143 }
1144 else
1145 {
1146 probeCommand = *findProbe;
1147 }
1148
1149 for (const nlohmann::json& probeJson : probeCommand)
1150 {
1151 const std::string* probe = probeJson.get_ptr<const std::string*>();
1152 if (probe == nullptr)
1153 {
1154 std::cerr << "Probe statement wasn't a string, can't parse";
1155 continue;
1156 }
1157 // Skip it if the probe cmd doesn't contain an interface.
Christopher Meis26fbbd52025-03-26 14:55:06 +01001158 if (probe::findProbeType(*probe))
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001159 {
1160 continue;
1161 }
1162
1163 // syntax requires probe before first open brace
1164 auto findStart = probe->find('(');
1165 if (findStart != std::string::npos)
1166 {
1167 std::string interface = probe->substr(0, findStart);
1168 interfaces.emplace(interface);
1169 }
1170 }
1171 it++;
1172 }
1173
1174 return interfaces;
1175}
1176
1177// Check if InterfacesAdded payload contains an iface that needs probing.
Patrick Williamsb7077432024-08-16 15:22:21 -04001178static bool iaContainsProbeInterface(
1179 sdbusplus::message_t& msg, const std::set<std::string>& probeInterfaces)
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001180{
1181 sdbusplus::message::object_path path;
1182 DBusObject interfaces;
1183 std::set<std::string> interfaceSet;
1184 std::set<std::string> intersect;
1185
1186 msg.read(path, interfaces);
1187
1188 std::for_each(interfaces.begin(), interfaces.end(),
1189 [&interfaceSet](const auto& iface) {
Patrick Williamsb7077432024-08-16 15:22:21 -04001190 interfaceSet.insert(iface.first);
1191 });
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001192
1193 std::set_intersection(interfaceSet.begin(), interfaceSet.end(),
1194 probeInterfaces.begin(), probeInterfaces.end(),
1195 std::inserter(intersect, intersect.end()));
1196 return !intersect.empty();
1197}
1198
1199// Check if InterfacesRemoved payload contains an iface that needs probing.
Patrick Williamsb7077432024-08-16 15:22:21 -04001200static bool irContainsProbeInterface(
1201 sdbusplus::message_t& msg, const std::set<std::string>& probeInterfaces)
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001202{
1203 sdbusplus::message::object_path path;
1204 std::set<std::string> interfaces;
1205 std::set<std::string> intersect;
1206
1207 msg.read(path, interfaces);
1208
1209 std::set_intersection(interfaces.begin(), interfaces.end(),
1210 probeInterfaces.begin(), probeInterfaces.end(),
1211 std::inserter(intersect, intersect.end()));
1212 return !intersect.empty();
1213}
1214
James Feist98132792019-07-09 13:29:09 -07001215int main()
James Feist75fdeeb2018-02-20 14:26:16 -08001216{
1217 // setup connection to dbus
Ed Tanous07d467b2021-02-23 14:48:37 -08001218 systemBus = std::make_shared<sdbusplus::asio::connection>(io);
1219 systemBus->request_name("xyz.openbmc_project.EntityManager");
James Feist4131aea2018-03-09 09:47:30 -08001220
Nan Zhoua3315672022-09-20 19:48:14 +00001221 // The EntityManager object itself doesn't expose any properties.
1222 // No need to set up ObjectManager for the |EntityManager| object.
1223 sdbusplus::asio::object_server objServer(systemBus, /*skipManager=*/true);
1224
1225 // All other objects that EntityManager currently support are under the
1226 // inventory subtree.
1227 // See the discussion at
1228 // https://discord.com/channels/775381525260664832/1018929092009144380
1229 objServer.add_manager("/xyz/openbmc_project/inventory");
James Feistfd1264a2018-05-03 12:10:00 -07001230
James Feist8f2710a2018-05-09 17:18:55 -07001231 std::shared_ptr<sdbusplus::asio::dbus_interface> entityIface =
1232 objServer.add_interface("/xyz/openbmc_project/EntityManager",
1233 "xyz.openbmc_project.EntityManager");
James Feistfd1264a2018-05-03 12:10:00 -07001234
James Feist4131aea2018-03-09 09:47:30 -08001235 // to keep reference to the match / filter objects so they don't get
1236 // destroyed
James Feist8f2710a2018-05-09 17:18:55 -07001237
1238 nlohmann::json systemConfiguration = nlohmann::json::object();
1239
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001240 std::set<std::string> probeInterfaces = getProbeInterfaces();
1241
Brad Bishopc76af0f2020-12-04 13:50:23 -05001242 // We need a poke from DBus for static providers that create all their
1243 // objects prior to claiming a well-known name, and thus don't emit any
1244 // org.freedesktop.DBus.Properties signals. Similarly if a process exits
1245 // for any reason, expected or otherwise, we'll need a poke to remove
1246 // entities from DBus.
Patrick Williams2af39222022-07-22 19:26:56 -05001247 sdbusplus::bus::match_t nameOwnerChangedMatch(
1248 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishopc76af0f2020-12-04 13:50:23 -05001249 sdbusplus::bus::match::rules::nameOwnerChanged(),
Patrick Williams7b8786f2022-10-10 10:23:37 -05001250 [&](sdbusplus::message_t& m) {
Patrick Williamsb7077432024-08-16 15:22:21 -04001251 auto [name, oldOwner,
1252 newOwner] = m.unpack<std::string, std::string, std::string>();
Patrick Williams7b8786f2022-10-10 10:23:37 -05001253
Patrick Williamsb7077432024-08-16 15:22:21 -04001254 if (name.starts_with(':'))
1255 {
1256 // We should do nothing with unique-name connections.
1257 return;
1258 }
Patrick Williams7b8786f2022-10-10 10:23:37 -05001259
Patrick Williamsb7077432024-08-16 15:22:21 -04001260 propertiesChangedCallback(systemConfiguration, objServer);
1261 });
Brad Bishop10a8c5f2020-12-07 21:40:07 -05001262 // We also need a poke from DBus when new interfaces are created or
1263 // destroyed.
Patrick Williams2af39222022-07-22 19:26:56 -05001264 sdbusplus::bus::match_t interfacesAddedMatch(
1265 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishop10a8c5f2020-12-07 21:40:07 -05001266 sdbusplus::bus::match::rules::interfacesAdded(),
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001267 [&](sdbusplus::message_t& msg) {
Patrick Williamsb7077432024-08-16 15:22:21 -04001268 if (iaContainsProbeInterface(msg, probeInterfaces))
1269 {
1270 propertiesChangedCallback(systemConfiguration, objServer);
1271 }
1272 });
Patrick Williams2af39222022-07-22 19:26:56 -05001273 sdbusplus::bus::match_t interfacesRemovedMatch(
1274 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishop10a8c5f2020-12-07 21:40:07 -05001275 sdbusplus::bus::match::rules::interfacesRemoved(),
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001276 [&](sdbusplus::message_t& msg) {
Patrick Williamsb7077432024-08-16 15:22:21 -04001277 if (irContainsProbeInterface(msg, probeInterfaces))
1278 {
1279 propertiesChangedCallback(systemConfiguration, objServer);
1280 }
1281 });
Brad Bishopc76af0f2020-12-04 13:50:23 -05001282
Ed Tanous49a888c2023-03-06 13:44:51 -08001283 boost::asio::post(io, [&]() {
1284 propertiesChangedCallback(systemConfiguration, objServer);
1285 });
James Feist4131aea2018-03-09 09:47:30 -08001286
James Feistfd1264a2018-05-03 12:10:00 -07001287 entityIface->register_method("ReScan", [&]() {
James Feist4dc617b2020-05-01 09:54:47 -07001288 propertiesChangedCallback(systemConfiguration, objServer);
James Feist75fdeeb2018-02-20 14:26:16 -08001289 });
John Edward Broadbentd97c6312023-10-26 20:32:07 +00001290 tryIfaceInitialize(entityIface);
James Feist8f2710a2018-05-09 17:18:55 -07001291
James Feist1df06a42019-04-11 14:23:04 -07001292 if (fwVersionIsSame())
1293 {
1294 if (std::filesystem::is_regular_file(currentConfiguration))
1295 {
1296 // this file could just be deleted, but it's nice for debug
1297 std::filesystem::create_directory(tempConfigDir);
1298 std::filesystem::remove(lastConfiguration);
1299 std::filesystem::copy(currentConfiguration, lastConfiguration);
1300 std::filesystem::remove(currentConfiguration);
1301
1302 std::ifstream jsonStream(lastConfiguration);
1303 if (jsonStream.good())
1304 {
1305 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
1306 if (data.is_discarded())
1307 {
Patrick Williamsb7077432024-08-16 15:22:21 -04001308 std::cerr
1309 << "syntax error in " << lastConfiguration << "\n";
James Feist1df06a42019-04-11 14:23:04 -07001310 }
1311 else
1312 {
1313 lastJson = std::move(data);
1314 }
1315 }
1316 else
1317 {
1318 std::cerr << "unable to open " << lastConfiguration << "\n";
1319 }
1320 }
1321 }
1322 else
1323 {
1324 // not an error, just logging at this level to make it in the journal
1325 std::cerr << "Clearing previous configuration\n";
1326 std::filesystem::remove(currentConfiguration);
1327 }
1328
1329 // some boards only show up after power is on, we want to not say they are
1330 // removed until the same state happens
Ed Tanous07d467b2021-02-23 14:48:37 -08001331 setupPowerMatch(systemBus);
James Feist1df06a42019-04-11 14:23:04 -07001332
James Feist1b2e2242018-01-30 13:45:19 -08001333 io.run();
James Feist3cb5fec2018-01-23 14:41:51 -08001334
1335 return 0;
James Feist75fdeeb2018-02-20 14:26:16 -08001336}