blob: 6a4580deba3101e5f0265d2e22654ae4f82b6a10 [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"
Benjamin Fairca2eb042022-09-13 06:40:42 +000021#include "topology.hpp"
Brad Bishope45d8c72022-05-25 15:12:53 -040022#include "utils.hpp"
23#include "variant_visitors.hpp"
James Feist481c5d52019-08-13 14:40:40 -070024
James Feist11be6672018-04-06 14:05:32 -070025#include <boost/algorithm/string/case_conv.hpp>
James Feistf5125b02019-06-06 11:27:43 -070026#include <boost/algorithm/string/classification.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080027#include <boost/algorithm/string/predicate.hpp>
28#include <boost/algorithm/string/replace.hpp>
James Feistf5125b02019-06-06 11:27:43 -070029#include <boost/algorithm/string/split.hpp>
James Feist02d2b932020-02-06 16:28:48 -080030#include <boost/asio/io_context.hpp>
Ed Tanous49a888c2023-03-06 13:44:51 -080031#include <boost/asio/post.hpp>
James Feistb1728ca2020-04-30 15:40:55 -070032#include <boost/asio/steady_timer.hpp>
James Feist3cb5fec2018-01-23 14:41:51 -080033#include <boost/container/flat_map.hpp>
34#include <boost/container/flat_set.hpp>
James Feistf5125b02019-06-06 11:27:43 -070035#include <boost/range/iterator_range.hpp>
James Feist8c505da2020-05-28 10:06:33 -070036#include <nlohmann/json.hpp>
37#include <sdbusplus/asio/connection.hpp>
38#include <sdbusplus/asio/object_server.hpp>
39
Igor Kononenko9fd87e52020-10-06 01:18:17 +030040#include <charconv>
James Feist637b3ef2019-04-15 16:35:30 -070041#include <filesystem>
James Feista465ccc2019-02-08 12:51:01 -080042#include <fstream>
Andrew Jefferye35d0ac2022-03-24 15:53:13 +103043#include <functional>
James Feista465ccc2019-02-08 12:51:01 -080044#include <iostream>
Andrew Jeffery666583b2021-12-01 15:50:12 +103045#include <map>
James Feista465ccc2019-02-08 12:51:01 -080046#include <regex>
James Feista465ccc2019-02-08 12:51:01 -080047#include <variant>
Andrew Jefferya9c58922021-06-01 09:28:59 +093048constexpr const char* hostConfigurationDirectory = SYSCONF_DIR "configurations";
James Feista465ccc2019-02-08 12:51:01 -080049constexpr const char* configurationDirectory = PACKAGE_DIR "configurations";
50constexpr const char* schemaDirectory = PACKAGE_DIR "configurations/schemas";
James Feist1df06a42019-04-11 14:23:04 -070051constexpr const char* tempConfigDir = "/tmp/configuration/";
52constexpr const char* lastConfiguration = "/tmp/configuration/last.json";
53constexpr const char* currentConfiguration = "/var/configuration/system.json";
James Feista465ccc2019-02-08 12:51:01 -080054constexpr const char* globalSchema = "global.json";
James Feistf1b14142019-04-10 15:22:09 -070055
Andrew Jeffery666583b2021-12-01 15:50:12 +103056const boost::container::flat_map<const char*, probe_type_codes, CmpStr>
Ed Tanous07d467b2021-02-23 14:48:37 -080057 probeTypes{{{"FALSE", probe_type_codes::FALSE_T},
58 {"TRUE", probe_type_codes::TRUE_T},
59 {"AND", probe_type_codes::AND},
60 {"OR", probe_type_codes::OR},
61 {"FOUND", probe_type_codes::FOUND},
62 {"MATCH_ONE", probe_type_codes::MATCH_ONE}}};
James Feist3cb5fec2018-01-23 14:41:51 -080063
Adrian Ambrożewiczc789fca2020-05-14 15:50:05 +020064static constexpr std::array<const char*, 6> settableInterfaces = {
65 "FanProfile", "Pid", "Pid.Zone", "Stepwise", "Thresholds", "Polling"};
James Feist68500ff2018-08-08 15:40:42 -070066using JsonVariantType =
James Feist338b8a72019-03-01 10:16:45 -080067 std::variant<std::vector<std::string>, std::vector<double>, std::string,
68 int64_t, uint64_t, double, int32_t, uint32_t, int16_t,
69 uint16_t, uint8_t, bool>;
James Feist3cb5fec2018-01-23 14:41:51 -080070
James Feistd58879a2019-09-11 11:26:07 -070071// store reference to all interfaces so we can destroy them later
72boost::container::flat_map<
James Feist02d2b932020-02-06 16:28:48 -080073 std::string, std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>>
James Feistd58879a2019-09-11 11:26:07 -070074 inventory;
75
James Feist3cb5fec2018-01-23 14:41:51 -080076// todo: pass this through nicer
Ed Tanous07d467b2021-02-23 14:48:37 -080077std::shared_ptr<sdbusplus::asio::connection> systemBus;
Andrew Jeffery47af65a2021-12-01 14:16:31 +103078nlohmann::json lastJson;
Matt Spinler6eb60972023-08-14 16:36:20 -050079Topology topology;
James Feist3cb5fec2018-01-23 14:41:51 -080080
James Feist02d2b932020-02-06 16:28:48 -080081boost::asio::io_context io;
82
Ed Tanous07d467b2021-02-23 14:48:37 -080083const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]");
84const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]");
James Feist1b2e2242018-01-30 13:45:19 -080085
Andrew Jeffery666583b2021-12-01 15:50:12 +103086FoundProbeTypeT findProbeType(const std::string& probe)
87{
88 boost::container::flat_map<const char*, probe_type_codes,
89 CmpStr>::const_iterator probeType;
90 for (probeType = probeTypes.begin(); probeType != probeTypes.end();
91 ++probeType)
92 {
93 if (probe.find(probeType->first) != std::string::npos)
94 {
95 return probeType;
96 }
97 }
98
99 return std::nullopt;
100}
101
James Feistd58879a2019-09-11 11:26:07 -0700102static std::shared_ptr<sdbusplus::asio::dbus_interface>
103 createInterface(sdbusplus::asio::object_server& objServer,
104 const std::string& path, const std::string& interface,
James Feist02d2b932020-02-06 16:28:48 -0800105 const std::string& parent, bool checkNull = false)
James Feistd58879a2019-09-11 11:26:07 -0700106{
James Feist02d2b932020-02-06 16:28:48 -0800107 // on first add we have no reason to check for null before add, as there
108 // won't be any. For dynamically added interfaces, we check for null so that
109 // a constant delete/add will not create a memory leak
110
111 auto ptr = objServer.add_interface(path, interface);
112 auto& dataVector = inventory[parent];
113 if (checkNull)
114 {
115 auto it = std::find_if(dataVector.begin(), dataVector.end(),
116 [](const auto& p) { return p.expired(); });
117 if (it != dataVector.end())
118 {
119 *it = ptr;
120 return ptr;
121 }
122 }
123 dataVector.emplace_back(ptr);
124 return ptr;
James Feistd58879a2019-09-11 11:26:07 -0700125}
126
James Feist8f2710a2018-05-09 17:18:55 -0700127// writes output files to persist data
James Feista465ccc2019-02-08 12:51:01 -0800128bool writeJsonFiles(const nlohmann::json& systemConfiguration)
James Feist1b2e2242018-01-30 13:45:19 -0800129{
James Feist1df06a42019-04-11 14:23:04 -0700130 std::filesystem::create_directory(configurationOutDir);
131 std::ofstream output(currentConfiguration);
James Feistbb43d022018-06-12 15:44:33 -0700132 if (!output.good())
133 {
134 return false;
135 }
James Feist1b2e2242018-01-30 13:45:19 -0800136 output << systemConfiguration.dump(4);
137 output.close();
James Feistbb43d022018-06-12 15:44:33 -0700138 return true;
James Feist8f2710a2018-05-09 17:18:55 -0700139}
James Feist1b2e2242018-01-30 13:45:19 -0800140
James Feist97a63f12018-05-17 13:50:57 -0700141template <typename JsonType>
James Feista465ccc2019-02-08 12:51:01 -0800142bool setJsonFromPointer(const std::string& ptrStr, const JsonType& value,
143 nlohmann::json& systemConfiguration)
James Feist97a63f12018-05-17 13:50:57 -0700144{
145 try
146 {
147 nlohmann::json::json_pointer ptr(ptrStr);
James Feista465ccc2019-02-08 12:51:01 -0800148 nlohmann::json& ref = systemConfiguration[ptr];
James Feist97a63f12018-05-17 13:50:57 -0700149 ref = value;
150 return true;
151 }
James Feist98132792019-07-09 13:29:09 -0700152 catch (const std::out_of_range&)
James Feist97a63f12018-05-17 13:50:57 -0700153 {
154 return false;
155 }
156}
James Feistbb43d022018-06-12 15:44:33 -0700157
James Feistebcc26b2019-03-22 12:30:43 -0700158// template function to add array as dbus property
159template <typename PropertyType>
160void addArrayToDbus(const std::string& name, const nlohmann::json& array,
161 sdbusplus::asio::dbus_interface* iface,
162 sdbusplus::asio::PropertyPermission permission,
163 nlohmann::json& systemConfiguration,
164 const std::string& jsonPointerString)
165{
166 std::vector<PropertyType> values;
167 for (const auto& property : array)
168 {
169 auto ptr = property.get_ptr<const PropertyType*>();
170 if (ptr != nullptr)
171 {
172 values.emplace_back(*ptr);
173 }
174 }
175
176 if (permission == sdbusplus::asio::PropertyPermission::readOnly)
177 {
178 iface->register_property(name, values);
179 }
180 else
181 {
182 iface->register_property(
183 name, values,
184 [&systemConfiguration,
185 jsonPointerString{std::string(jsonPointerString)}](
186 const std::vector<PropertyType>& newVal,
187 std::vector<PropertyType>& val) {
Patrick Williamsdf190612023-05-10 07:51:34 -0500188 val = newVal;
189 if (!setJsonFromPointer(jsonPointerString, val,
190 systemConfiguration))
191 {
192 std::cerr << "error setting json field\n";
193 return -1;
194 }
195 if (!writeJsonFiles(systemConfiguration))
196 {
197 std::cerr << "error setting json file\n";
198 return -1;
199 }
200 return 1;
Patrick Williamsb9dd7f82023-10-20 11:20:11 -0500201 });
James Feistebcc26b2019-03-22 12:30:43 -0700202 }
203}
204
James Feistbb43d022018-06-12 15:44:33 -0700205template <typename PropertyType>
Ed Tanous3013fb42022-07-09 08:27:06 -0700206void addProperty(const std::string& name, const PropertyType& value,
James Feista465ccc2019-02-08 12:51:01 -0800207 sdbusplus::asio::dbus_interface* iface,
208 nlohmann::json& systemConfiguration,
209 const std::string& jsonPointerString,
James Feistbb43d022018-06-12 15:44:33 -0700210 sdbusplus::asio::PropertyPermission permission)
211{
212 if (permission == sdbusplus::asio::PropertyPermission::readOnly)
213 {
Ed Tanous3013fb42022-07-09 08:27:06 -0700214 iface->register_property(name, value);
James Feistbb43d022018-06-12 15:44:33 -0700215 return;
216 }
James Feist68500ff2018-08-08 15:40:42 -0700217 iface->register_property(
Ed Tanous3013fb42022-07-09 08:27:06 -0700218 name, value,
James Feist68500ff2018-08-08 15:40:42 -0700219 [&systemConfiguration,
220 jsonPointerString{std::string(jsonPointerString)}](
James Feista465ccc2019-02-08 12:51:01 -0800221 const PropertyType& newVal, PropertyType& val) {
Patrick Williamsdf190612023-05-10 07:51:34 -0500222 val = newVal;
223 if (!setJsonFromPointer(jsonPointerString, val, systemConfiguration))
224 {
225 std::cerr << "error setting json field\n";
226 return -1;
227 }
228 if (!writeJsonFiles(systemConfiguration))
229 {
230 std::cerr << "error setting json file\n";
231 return -1;
232 }
233 return 1;
Patrick Williamsb9dd7f82023-10-20 11:20:11 -0500234 });
James Feistc6248a52018-08-14 10:09:45 -0700235}
236
237void createDeleteObjectMethod(
James Feista465ccc2019-02-08 12:51:01 -0800238 const std::string& jsonPointerPath,
239 const std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
240 sdbusplus::asio::object_server& objServer,
241 nlohmann::json& systemConfiguration)
James Feistc6248a52018-08-14 10:09:45 -0700242{
243 std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface;
Patrick Williamsdf190612023-05-10 07:51:34 -0500244 iface->register_method("Delete",
245 [&objServer, &systemConfiguration, interface,
246 jsonPointerPath{std::string(jsonPointerPath)}]() {
247 std::shared_ptr<sdbusplus::asio::dbus_interface> dbusInterface =
248 interface.lock();
249 if (!dbusInterface)
250 {
251 // this technically can't happen as the pointer is pointing to
252 // us
253 throw DBusInternalError();
254 }
255 nlohmann::json::json_pointer ptr(jsonPointerPath);
256 systemConfiguration[ptr] = nullptr;
James Feistc6248a52018-08-14 10:09:45 -0700257
Patrick Williamsdf190612023-05-10 07:51:34 -0500258 // todo(james): dig through sdbusplus to find out why we can't
259 // delete it in a method call
260 boost::asio::post(io, [&objServer, dbusInterface]() mutable {
261 objServer.remove_interface(dbusInterface);
James Feist68500ff2018-08-08 15:40:42 -0700262 });
Patrick Williamsdf190612023-05-10 07:51:34 -0500263
264 if (!writeJsonFiles(systemConfiguration))
265 {
266 std::cerr << "error setting json file\n";
267 throw DBusInternalError();
268 }
269 });
James Feistbb43d022018-06-12 15:44:33 -0700270}
271
James Feist1b2e2242018-01-30 13:45:19 -0800272// adds simple json types to interface's properties
James Feistbb43d022018-06-12 15:44:33 -0700273void populateInterfaceFromJson(
James Feista465ccc2019-02-08 12:51:01 -0800274 nlohmann::json& systemConfiguration, const std::string& jsonPointerPath,
275 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
276 nlohmann::json& dict, sdbusplus::asio::object_server& objServer,
James Feistbb43d022018-06-12 15:44:33 -0700277 sdbusplus::asio::PropertyPermission permission =
278 sdbusplus::asio::PropertyPermission::readOnly)
James Feist1b2e2242018-01-30 13:45:19 -0800279{
Patrick Williams2594d362022-09-28 06:46:24 -0500280 for (const auto& [key, value] : dict.items())
James Feist1b2e2242018-01-30 13:45:19 -0800281 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030282 auto type = value.type();
James Feist8f2710a2018-05-09 17:18:55 -0700283 bool array = false;
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030284 if (value.type() == nlohmann::json::value_t::array)
James Feist8f2710a2018-05-09 17:18:55 -0700285 {
286 array = true;
Ed Tanous3013fb42022-07-09 08:27:06 -0700287 if (value.empty())
James Feist8f2710a2018-05-09 17:18:55 -0700288 {
289 continue;
290 }
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030291 type = value[0].type();
James Feist8f2710a2018-05-09 17:18:55 -0700292 bool isLegal = true;
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030293 for (const auto& arrayItem : value)
James Feist8f2710a2018-05-09 17:18:55 -0700294 {
295 if (arrayItem.type() != type)
296 {
297 isLegal = false;
298 break;
299 }
300 }
301 if (!isLegal)
302 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030303 std::cerr << "dbus format error" << value << "\n";
James Feist8f2710a2018-05-09 17:18:55 -0700304 continue;
305 }
James Feista218ddb2019-04-11 14:01:31 -0700306 }
307 if (type == nlohmann::json::value_t::object)
308 {
309 continue; // handled elsewhere
James Feist8f2710a2018-05-09 17:18:55 -0700310 }
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030311
312 std::string path = jsonPointerPath;
313 path.append("/").append(key);
James Feistbb43d022018-06-12 15:44:33 -0700314 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
315 {
316 // all setable numbers are doubles as it is difficult to always
317 // create a configuration file with all whole numbers as decimals
318 // i.e. 1.0
James Feistebcc26b2019-03-22 12:30:43 -0700319 if (array)
320 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030321 if (value[0].is_number())
James Feistebcc26b2019-03-22 12:30:43 -0700322 {
323 type = nlohmann::json::value_t::number_float;
324 }
325 }
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030326 else if (value.is_number())
James Feistbb43d022018-06-12 15:44:33 -0700327 {
328 type = nlohmann::json::value_t::number_float;
329 }
330 }
331
James Feist8f2710a2018-05-09 17:18:55 -0700332 switch (type)
James Feist1b2e2242018-01-30 13:45:19 -0800333 {
James Feist9eb0b582018-04-27 12:15:46 -0700334 case (nlohmann::json::value_t::boolean):
335 {
James Feist8f2710a2018-05-09 17:18:55 -0700336 if (array)
337 {
338 // todo: array of bool isn't detected correctly by
339 // sdbusplus, change it to numbers
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030340 addArrayToDbus<uint64_t>(key, value, iface.get(),
341 permission, systemConfiguration,
342 path);
James Feist8f2710a2018-05-09 17:18:55 -0700343 }
James Feistbb43d022018-06-12 15:44:33 -0700344
James Feist97a63f12018-05-17 13:50:57 -0700345 else
346 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030347 addProperty(key, value.get<bool>(), iface.get(),
348 systemConfiguration, path, permission);
James Feist97a63f12018-05-17 13:50:57 -0700349 }
James Feist9eb0b582018-04-27 12:15:46 -0700350 break;
351 }
352 case (nlohmann::json::value_t::number_integer):
353 {
James Feist8f2710a2018-05-09 17:18:55 -0700354 if (array)
355 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030356 addArrayToDbus<int64_t>(key, value, iface.get(), permission,
Andrew Jeffery029ee282022-03-25 13:11:36 +1030357 systemConfiguration, path);
James Feist97a63f12018-05-17 13:50:57 -0700358 }
359 else
360 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030361 addProperty(key, value.get<int64_t>(), iface.get(),
362 systemConfiguration, path,
James Feistbb43d022018-06-12 15:44:33 -0700363 sdbusplus::asio::PropertyPermission::readOnly);
James Feist97a63f12018-05-17 13:50:57 -0700364 }
James Feist9eb0b582018-04-27 12:15:46 -0700365 break;
366 }
367 case (nlohmann::json::value_t::number_unsigned):
368 {
James Feist8f2710a2018-05-09 17:18:55 -0700369 if (array)
370 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030371 addArrayToDbus<uint64_t>(key, value, iface.get(),
372 permission, systemConfiguration,
373 path);
James Feist97a63f12018-05-17 13:50:57 -0700374 }
375 else
376 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030377 addProperty(key, value.get<uint64_t>(), iface.get(),
Andrew Jeffery029ee282022-03-25 13:11:36 +1030378 systemConfiguration, path,
James Feistbb43d022018-06-12 15:44:33 -0700379 sdbusplus::asio::PropertyPermission::readOnly);
James Feist97a63f12018-05-17 13:50:57 -0700380 }
James Feist9eb0b582018-04-27 12:15:46 -0700381 break;
382 }
383 case (nlohmann::json::value_t::number_float):
384 {
James Feist8f2710a2018-05-09 17:18:55 -0700385 if (array)
386 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030387 addArrayToDbus<double>(key, value, iface.get(), permission,
Andrew Jeffery029ee282022-03-25 13:11:36 +1030388 systemConfiguration, path);
James Feist8f2710a2018-05-09 17:18:55 -0700389 }
James Feistbb43d022018-06-12 15:44:33 -0700390
James Feist97a63f12018-05-17 13:50:57 -0700391 else
392 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030393 addProperty(key, value.get<double>(), iface.get(),
394 systemConfiguration, path, permission);
James Feist97a63f12018-05-17 13:50:57 -0700395 }
James Feist9eb0b582018-04-27 12:15:46 -0700396 break;
397 }
398 case (nlohmann::json::value_t::string):
399 {
James Feist8f2710a2018-05-09 17:18:55 -0700400 if (array)
401 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030402 addArrayToDbus<std::string>(key, value, iface.get(),
403 permission, systemConfiguration,
404 path);
James Feist97a63f12018-05-17 13:50:57 -0700405 }
406 else
407 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030408 addProperty(key, value.get<std::string>(), iface.get(),
409 systemConfiguration, path, permission);
James Feist97a63f12018-05-17 13:50:57 -0700410 }
James Feist9eb0b582018-04-27 12:15:46 -0700411 break;
412 }
James Feist0eb40352019-04-09 14:44:04 -0700413 default:
414 {
James Feista218ddb2019-04-11 14:01:31 -0700415 std::cerr << "Unexpected json type in system configuration "
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030416 << key << ": " << value.type_name() << "\n";
James Feist0eb40352019-04-09 14:44:04 -0700417 break;
418 }
James Feist1b2e2242018-01-30 13:45:19 -0800419 }
420 }
James Feistc6248a52018-08-14 10:09:45 -0700421 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
422 {
423 createDeleteObjectMethod(jsonPointerPath, iface, objServer,
424 systemConfiguration);
425 }
James Feist8f2710a2018-05-09 17:18:55 -0700426 iface->initialize();
James Feist1b2e2242018-01-30 13:45:19 -0800427}
428
James Feista465ccc2019-02-08 12:51:01 -0800429sdbusplus::asio::PropertyPermission getPermission(const std::string& interface)
James Feistc6248a52018-08-14 10:09:45 -0700430{
431 return std::find(settableInterfaces.begin(), settableInterfaces.end(),
432 interface) != settableInterfaces.end()
433 ? sdbusplus::asio::PropertyPermission::readWrite
434 : sdbusplus::asio::PropertyPermission::readOnly;
435}
436
James Feista465ccc2019-02-08 12:51:01 -0800437void createAddObjectMethod(const std::string& jsonPointerPath,
438 const std::string& path,
439 nlohmann::json& systemConfiguration,
James Feistd58879a2019-09-11 11:26:07 -0700440 sdbusplus::asio::object_server& objServer,
441 const std::string& board)
James Feist68500ff2018-08-08 15:40:42 -0700442{
James Feistd58879a2019-09-11 11:26:07 -0700443 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = createInterface(
444 objServer, path, "xyz.openbmc_project.AddObject", board);
James Feist68500ff2018-08-08 15:40:42 -0700445
446 iface->register_method(
447 "AddObject",
448 [&systemConfiguration, &objServer,
James Feistd58879a2019-09-11 11:26:07 -0700449 jsonPointerPath{std::string(jsonPointerPath)}, path{std::string(path)},
450 board](const boost::container::flat_map<std::string, JsonVariantType>&
451 data) {
Patrick Williamsdf190612023-05-10 07:51:34 -0500452 nlohmann::json::json_pointer ptr(jsonPointerPath);
453 nlohmann::json& base = systemConfiguration[ptr];
454 auto findExposes = base.find("Exposes");
James Feist68500ff2018-08-08 15:40:42 -0700455
Patrick Williamsdf190612023-05-10 07:51:34 -0500456 if (findExposes == base.end())
457 {
458 throw std::invalid_argument("Entity must have children.");
459 }
460
461 // this will throw invalid-argument to sdbusplus if invalid json
462 nlohmann::json newData{};
463 for (const auto& item : data)
464 {
465 nlohmann::json& newJson = newData[item.first];
466 std::visit(
467 [&newJson](auto&& val) {
468 newJson = std::forward<decltype(val)>(val);
Patrick Williamsb9dd7f82023-10-20 11:20:11 -0500469 },
Patrick Williamsdf190612023-05-10 07:51:34 -0500470 item.second);
471 }
472
473 auto findName = newData.find("Name");
474 auto findType = newData.find("Type");
475 if (findName == newData.end() || findType == newData.end())
476 {
477 throw std::invalid_argument("AddObject missing Name or Type");
478 }
479 const std::string* type = findType->get_ptr<const std::string*>();
480 const std::string* name = findName->get_ptr<const std::string*>();
481 if (type == nullptr || name == nullptr)
482 {
483 throw std::invalid_argument("Type and Name must be a string.");
484 }
485
486 bool foundNull = false;
487 size_t lastIndex = 0;
488 // we add in the "exposes"
489 for (const auto& expose : *findExposes)
490 {
491 if (expose.is_null())
James Feist68500ff2018-08-08 15:40:42 -0700492 {
Patrick Williamsdf190612023-05-10 07:51:34 -0500493 foundNull = true;
494 continue;
James Feist68500ff2018-08-08 15:40:42 -0700495 }
496
Patrick Williamsdf190612023-05-10 07:51:34 -0500497 if (expose["Name"] == *name && expose["Type"] == *type)
James Feist68500ff2018-08-08 15:40:42 -0700498 {
499 throw std::invalid_argument(
Patrick Williamsdf190612023-05-10 07:51:34 -0500500 "Field already in JSON, not adding");
James Feist68500ff2018-08-08 15:40:42 -0700501 }
Patrick Williamsdf190612023-05-10 07:51:34 -0500502
James Feist02d2b932020-02-06 16:28:48 -0800503 if (foundNull)
504 {
Patrick Williamsdf190612023-05-10 07:51:34 -0500505 continue;
James Feist02d2b932020-02-06 16:28:48 -0800506 }
James Feist68500ff2018-08-08 15:40:42 -0700507
Patrick Williamsdf190612023-05-10 07:51:34 -0500508 lastIndex++;
509 }
James Feistd58879a2019-09-11 11:26:07 -0700510
Patrick Williamsdf190612023-05-10 07:51:34 -0500511 std::ifstream schemaFile(std::string(schemaDirectory) + "/" +
512 boost::to_lower_copy(*type) + ".json");
513 // todo(james) we might want to also make a list of 'can add'
514 // interfaces but for now I think the assumption if there is a
515 // schema avaliable that it is allowed to update is fine
516 if (!schemaFile.good())
517 {
518 throw std::invalid_argument(
519 "No schema avaliable, cannot validate.");
520 }
521 nlohmann::json schema = nlohmann::json::parse(schemaFile, nullptr,
522 false);
523 if (schema.is_discarded())
524 {
525 std::cerr << "Schema not legal" << *type << ".json\n";
526 throw DBusInternalError();
527 }
528 if (!validateJson(schema, newData))
529 {
530 throw std::invalid_argument("Data does not match schema");
531 }
532 if (foundNull)
533 {
534 findExposes->at(lastIndex) = newData;
535 }
536 else
537 {
538 findExposes->push_back(newData);
539 }
540 if (!writeJsonFiles(systemConfiguration))
541 {
542 std::cerr << "Error writing json files\n";
543 throw DBusInternalError();
544 }
545 std::string dbusName = *name;
546
547 std::regex_replace(dbusName.begin(), dbusName.begin(), dbusName.end(),
548 illegalDbusMemberRegex, "_");
549
550 std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
551 createInterface(objServer, path + "/" + dbusName,
552 "xyz.openbmc_project.Configuration." + *type, board,
553 true);
554 // permission is read-write, as since we just created it, must be
555 // runtime modifiable
556 populateInterfaceFromJson(
557 systemConfiguration,
558 jsonPointerPath + "/Exposes/" + std::to_string(lastIndex),
559 interface, newData, objServer,
560 sdbusplus::asio::PropertyPermission::readWrite);
Patrick Williamsb9dd7f82023-10-20 11:20:11 -0500561 });
James Feist68500ff2018-08-08 15:40:42 -0700562 iface->initialize();
563}
564
James Feista465ccc2019-02-08 12:51:01 -0800565void postToDbus(const nlohmann::json& newConfiguration,
566 nlohmann::json& systemConfiguration,
567 sdbusplus::asio::object_server& objServer)
James Feist75fdeeb2018-02-20 14:26:16 -0800568
James Feist1b2e2242018-01-30 13:45:19 -0800569{
Matt Spinler6eb60972023-08-14 16:36:20 -0500570 std::map<std::string, std::string> newBoards; // path -> name
Benjamin Fairca2eb042022-09-13 06:40:42 +0000571
James Feist97a63f12018-05-17 13:50:57 -0700572 // iterate through boards
Patrick Williams2594d362022-09-28 06:46:24 -0500573 for (const auto& [boardId, boardConfig] : newConfiguration.items())
James Feist1b2e2242018-01-30 13:45:19 -0800574 {
Matt Spinler3d1909a2023-08-10 16:39:44 -0500575 std::string boardName = boardConfig["Name"];
576 std::string boardNameOrig = boardConfig["Name"];
Andrew Jeffery13132df2022-03-25 13:29:41 +1030577 std::string jsonPointerPath = "/" + boardId;
James Feist97a63f12018-05-17 13:50:57 -0700578 // loop through newConfiguration, but use values from system
579 // configuration to be able to modify via dbus later
Andrew Jeffery13132df2022-03-25 13:29:41 +1030580 auto boardValues = systemConfiguration[boardId];
James Feistd63d18a2018-07-19 15:23:45 -0700581 auto findBoardType = boardValues.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -0800582 std::string boardType;
583 if (findBoardType != boardValues.end() &&
584 findBoardType->type() == nlohmann::json::value_t::string)
585 {
586 boardType = findBoardType->get<std::string>();
587 std::regex_replace(boardType.begin(), boardType.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800588 boardType.end(), illegalDbusMemberRegex, "_");
James Feist1b2e2242018-01-30 13:45:19 -0800589 }
590 else
591 {
Matt Spinler3d1909a2023-08-10 16:39:44 -0500592 std::cerr << "Unable to find type for " << boardName
James Feist1b2e2242018-01-30 13:45:19 -0800593 << " reverting to Chassis.\n";
594 boardType = "Chassis";
595 }
James Feist11be6672018-04-06 14:05:32 -0700596 std::string boardtypeLower = boost::algorithm::to_lower_copy(boardType);
James Feist1b2e2242018-01-30 13:45:19 -0800597
Matt Spinler3d1909a2023-08-10 16:39:44 -0500598 std::regex_replace(boardName.begin(), boardName.begin(),
599 boardName.end(), illegalDbusMemberRegex, "_");
600 std::string boardPath = "/xyz/openbmc_project/inventory/system/";
601 boardPath += boardtypeLower;
602 boardPath += "/";
603 boardPath += boardName;
James Feist1b2e2242018-01-30 13:45:19 -0800604
James Feistd58879a2019-09-11 11:26:07 -0700605 std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
Matt Spinler3d1909a2023-08-10 16:39:44 -0500606 createInterface(objServer, boardPath,
607 "xyz.openbmc_project.Inventory.Item", boardName);
James Feist68500ff2018-08-08 15:40:42 -0700608
James Feistd58879a2019-09-11 11:26:07 -0700609 std::shared_ptr<sdbusplus::asio::dbus_interface> boardIface =
Matt Spinler3d1909a2023-08-10 16:39:44 -0500610 createInterface(objServer, boardPath,
James Feistd58879a2019-09-11 11:26:07 -0700611 "xyz.openbmc_project.Inventory.Item." + boardType,
Matt Spinler3d1909a2023-08-10 16:39:44 -0500612 boardNameOrig);
James Feist11be6672018-04-06 14:05:32 -0700613
Matt Spinler3d1909a2023-08-10 16:39:44 -0500614 createAddObjectMethod(jsonPointerPath, boardPath, systemConfiguration,
615 objServer, boardNameOrig);
James Feist68500ff2018-08-08 15:40:42 -0700616
James Feist97a63f12018-05-17 13:50:57 -0700617 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
James Feistc6248a52018-08-14 10:09:45 -0700618 boardIface, boardValues, objServer);
James Feist97a63f12018-05-17 13:50:57 -0700619 jsonPointerPath += "/";
620 // iterate through board properties
Patrick Williams2594d362022-09-28 06:46:24 -0500621 for (const auto& [propName, propValue] : boardValues.items())
James Feist11be6672018-04-06 14:05:32 -0700622 {
Andrew Jefferya96950d2022-03-25 13:32:46 +1030623 if (propValue.type() == nlohmann::json::value_t::object)
James Feist11be6672018-04-06 14:05:32 -0700624 {
James Feistd58879a2019-09-11 11:26:07 -0700625 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
Matt Spinler3d1909a2023-08-10 16:39:44 -0500626 createInterface(objServer, boardPath, propName,
627 boardNameOrig);
James Feistd58879a2019-09-11 11:26:07 -0700628
James Feistc6248a52018-08-14 10:09:45 -0700629 populateInterfaceFromJson(systemConfiguration,
Andrew Jefferya96950d2022-03-25 13:32:46 +1030630 jsonPointerPath + propName, iface,
631 propValue, objServer);
James Feist11be6672018-04-06 14:05:32 -0700632 }
633 }
James Feist97a63f12018-05-17 13:50:57 -0700634
James Feist1e3e6982018-08-03 16:09:28 -0700635 auto exposes = boardValues.find("Exposes");
James Feist1b2e2242018-01-30 13:45:19 -0800636 if (exposes == boardValues.end())
637 {
638 continue;
639 }
James Feist97a63f12018-05-17 13:50:57 -0700640 // iterate through exposes
James Feist1e3e6982018-08-03 16:09:28 -0700641 jsonPointerPath += "Exposes/";
James Feist97a63f12018-05-17 13:50:57 -0700642
643 // store the board level pointer so we can modify it on the way down
644 std::string jsonPointerPathBoard = jsonPointerPath;
645 size_t exposesIndex = -1;
James Feista465ccc2019-02-08 12:51:01 -0800646 for (auto& item : *exposes)
James Feist1b2e2242018-01-30 13:45:19 -0800647 {
James Feist97a63f12018-05-17 13:50:57 -0700648 exposesIndex++;
649 jsonPointerPath = jsonPointerPathBoard;
650 jsonPointerPath += std::to_string(exposesIndex);
651
James Feistd63d18a2018-07-19 15:23:45 -0700652 auto findName = item.find("Name");
James Feist1b2e2242018-01-30 13:45:19 -0800653 if (findName == item.end())
654 {
655 std::cerr << "cannot find name in field " << item << "\n";
656 continue;
657 }
James Feist1e3e6982018-08-03 16:09:28 -0700658 auto findStatus = item.find("Status");
James Feist1b2e2242018-01-30 13:45:19 -0800659 // if status is not found it is assumed to be status = 'okay'
660 if (findStatus != item.end())
661 {
662 if (*findStatus == "disabled")
663 {
664 continue;
665 }
666 }
James Feistd63d18a2018-07-19 15:23:45 -0700667 auto findType = item.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -0800668 std::string itemType;
669 if (findType != item.end())
670 {
671 itemType = findType->get<std::string>();
672 std::regex_replace(itemType.begin(), itemType.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800673 itemType.end(), illegalDbusPathRegex, "_");
James Feist1b2e2242018-01-30 13:45:19 -0800674 }
675 else
676 {
677 itemType = "unknown";
678 }
679 std::string itemName = findName->get<std::string>();
680 std::regex_replace(itemName.begin(), itemName.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800681 itemName.end(), illegalDbusMemberRegex, "_");
Matt Spinler3d1909a2023-08-10 16:39:44 -0500682 std::string ifacePath = boardPath;
Ed Tanous07d467b2021-02-23 14:48:37 -0800683 ifacePath += "/";
684 ifacePath += itemName;
James Feistc6248a52018-08-14 10:09:45 -0700685
James Feistd58879a2019-09-11 11:26:07 -0700686 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface =
Ed Tanous07d467b2021-02-23 14:48:37 -0800687 createInterface(objServer, ifacePath,
James Feistd58879a2019-09-11 11:26:07 -0700688 "xyz.openbmc_project.Configuration." + itemType,
Matt Spinler3d1909a2023-08-10 16:39:44 -0500689 boardNameOrig);
James Feist1b2e2242018-01-30 13:45:19 -0800690
Sui Chen74ebe592022-09-13 10:22:03 -0700691 if (itemType == "BMC")
692 {
693 std::shared_ptr<sdbusplus::asio::dbus_interface> bmcIface =
694 createInterface(objServer, ifacePath,
695 "xyz.openbmc_project.Inventory.Item.Bmc",
Matt Spinler3d1909a2023-08-10 16:39:44 -0500696 boardNameOrig);
Sui Chen74ebe592022-09-13 10:22:03 -0700697 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
698 bmcIface, item, objServer,
699 getPermission(itemType));
700 }
Edward Leeeb587b42023-03-08 18:59:04 +0000701 else if (itemType == "System")
702 {
703 std::shared_ptr<sdbusplus::asio::dbus_interface> systemIface =
704 createInterface(objServer, ifacePath,
705 "xyz.openbmc_project.Inventory.Item.System",
Matt Spinler3d1909a2023-08-10 16:39:44 -0500706 boardNameOrig);
Edward Leeeb587b42023-03-08 18:59:04 +0000707 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
708 systemIface, item, objServer,
709 getPermission(itemType));
710 }
Sui Chen74ebe592022-09-13 10:22:03 -0700711
James Feist97a63f12018-05-17 13:50:57 -0700712 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
James Feistc6248a52018-08-14 10:09:45 -0700713 itemIface, item, objServer,
714 getPermission(itemType));
James Feist1b2e2242018-01-30 13:45:19 -0800715
Patrick Williams2594d362022-09-28 06:46:24 -0500716 for (const auto& [name, config] : item.items())
James Feist1b2e2242018-01-30 13:45:19 -0800717 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030718 jsonPointerPath = jsonPointerPathBoard;
719 jsonPointerPath.append(std::to_string(exposesIndex))
720 .append("/")
721 .append(name);
722 if (config.type() == nlohmann::json::value_t::object)
James Feist1b2e2242018-01-30 13:45:19 -0800723 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030724 std::string ifaceName =
725 "xyz.openbmc_project.Configuration.";
726 ifaceName.append(itemType).append(".").append(name);
James Feist97a63f12018-05-17 13:50:57 -0700727
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030728 std::shared_ptr<sdbusplus::asio::dbus_interface>
729 objectIface = createInterface(objServer, ifacePath,
Matt Spinler3d1909a2023-08-10 16:39:44 -0500730 ifaceName, boardNameOrig);
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030731
732 populateInterfaceFromJson(
733 systemConfiguration, jsonPointerPath, objectIface,
734 config, objServer, getPermission(name));
James Feist1b2e2242018-01-30 13:45:19 -0800735 }
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030736 else if (config.type() == nlohmann::json::value_t::array)
James Feist1b2e2242018-01-30 13:45:19 -0800737 {
738 size_t index = 0;
Ed Tanous3013fb42022-07-09 08:27:06 -0700739 if (config.empty())
James Feist1b2e2242018-01-30 13:45:19 -0800740 {
James Feist8f2710a2018-05-09 17:18:55 -0700741 continue;
742 }
743 bool isLegal = true;
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030744 auto type = config[0].type();
James Feist8f2710a2018-05-09 17:18:55 -0700745 if (type != nlohmann::json::value_t::object)
746 {
747 continue;
748 }
749
750 // verify legal json
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030751 for (const auto& arrayItem : config)
James Feist8f2710a2018-05-09 17:18:55 -0700752 {
753 if (arrayItem.type() != type)
James Feist1b2e2242018-01-30 13:45:19 -0800754 {
James Feist8f2710a2018-05-09 17:18:55 -0700755 isLegal = false;
James Feist1b2e2242018-01-30 13:45:19 -0800756 break;
757 }
James Feist8f2710a2018-05-09 17:18:55 -0700758 }
759 if (!isLegal)
760 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030761 std::cerr << "dbus format error" << config << "\n";
James Feist8f2710a2018-05-09 17:18:55 -0700762 break;
763 }
764
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030765 for (auto& arrayItem : config)
James Feist8f2710a2018-05-09 17:18:55 -0700766 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030767 std::string ifaceName =
768 "xyz.openbmc_project.Configuration.";
769 ifaceName.append(itemType).append(".").append(name);
770 ifaceName.append(std::to_string(index));
James Feist97a63f12018-05-17 13:50:57 -0700771
James Feistd58879a2019-09-11 11:26:07 -0700772 std::shared_ptr<sdbusplus::asio::dbus_interface>
773 objectIface = createInterface(
Matt Spinler3d1909a2023-08-10 16:39:44 -0500774 objServer, ifacePath, ifaceName, boardNameOrig);
James Feistd58879a2019-09-11 11:26:07 -0700775
James Feistc6248a52018-08-14 10:09:45 -0700776 populateInterfaceFromJson(
777 systemConfiguration,
778 jsonPointerPath + "/" + std::to_string(index),
779 objectIface, arrayItem, objServer,
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030780 getPermission(name));
James Feistbb43d022018-06-12 15:44:33 -0700781 index++;
James Feist1b2e2242018-01-30 13:45:19 -0800782 }
783 }
784 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000785
Matt Spinler6eb60972023-08-14 16:36:20 -0500786 topology.addBoard(boardPath, boardType, boardNameOrig, item);
James Feist1b2e2242018-01-30 13:45:19 -0800787 }
Matt Spinler6eb60972023-08-14 16:36:20 -0500788
789 newBoards.emplace(boardPath, boardNameOrig);
James Feist1b2e2242018-01-30 13:45:19 -0800790 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000791
Matt Spinler6eb60972023-08-14 16:36:20 -0500792 for (const auto& [assocPath, assocPropValue] :
793 topology.getAssocs(newBoards))
Benjamin Fairca2eb042022-09-13 06:40:42 +0000794 {
Matt Spinler6eb60972023-08-14 16:36:20 -0500795 auto findBoard = newBoards.find(assocPath);
796 if (findBoard == newBoards.end())
797 {
798 continue;
799 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000800
Matt Spinler6eb60972023-08-14 16:36:20 -0500801 auto ifacePtr = createInterface(
802 objServer, assocPath, "xyz.openbmc_project.Association.Definitions",
803 findBoard->second);
804
805 ifacePtr->register_property("Associations", assocPropValue);
Benjamin Fairca2eb042022-09-13 06:40:42 +0000806 ifacePtr->initialize();
807 }
James Feist1b2e2242018-01-30 13:45:19 -0800808}
809
James Feist8f2710a2018-05-09 17:18:55 -0700810// reads json files out of the filesystem
Andrew Jefferyf3311792022-03-29 22:09:00 +1030811bool loadConfigurations(std::list<nlohmann::json>& configurations)
James Feist3cb5fec2018-01-23 14:41:51 -0800812{
813 // find configuration files
Ed Tanous072e25d2018-12-16 21:45:20 -0800814 std::vector<std::filesystem::path> jsonPaths;
Andrew Jefferya9c58922021-06-01 09:28:59 +0930815 if (!findFiles(
816 std::vector<std::filesystem::path>{configurationDirectory,
817 hostConfigurationDirectory},
818 R"(.*\.json)", jsonPaths))
James Feist3cb5fec2018-01-23 14:41:51 -0800819 {
820 std::cerr << "Unable to find any configuration files in "
James Feistb4383f42018-08-06 16:54:10 -0700821 << configurationDirectory << "\n";
James Feist75fdeeb2018-02-20 14:26:16 -0800822 return false;
James Feist3cb5fec2018-01-23 14:41:51 -0800823 }
James Feistb4383f42018-08-06 16:54:10 -0700824
825 std::ifstream schemaStream(std::string(schemaDirectory) + "/" +
826 globalSchema);
827 if (!schemaStream.good())
828 {
829 std::cerr
830 << "Cannot open schema file, cannot validate JSON, exiting\n\n";
831 std::exit(EXIT_FAILURE);
Ed Tanous072e25d2018-12-16 21:45:20 -0800832 return false;
James Feistb4383f42018-08-06 16:54:10 -0700833 }
834 nlohmann::json schema = nlohmann::json::parse(schemaStream, nullptr, false);
835 if (schema.is_discarded())
836 {
837 std::cerr
838 << "Illegal schema file detected, cannot validate JSON, exiting\n";
839 std::exit(EXIT_FAILURE);
Ed Tanous072e25d2018-12-16 21:45:20 -0800840 return false;
James Feistb4383f42018-08-06 16:54:10 -0700841 }
842
James Feista465ccc2019-02-08 12:51:01 -0800843 for (auto& jsonPath : jsonPaths)
James Feist3cb5fec2018-01-23 14:41:51 -0800844 {
845 std::ifstream jsonStream(jsonPath.c_str());
846 if (!jsonStream.good())
847 {
848 std::cerr << "unable to open " << jsonPath.string() << "\n";
849 continue;
850 }
851 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
852 if (data.is_discarded())
853 {
854 std::cerr << "syntax error in " << jsonPath.string() << "\n";
855 continue;
856 }
James Feist8da99192019-01-24 08:20:16 -0800857 /*
858 * todo(james): reenable this once less things are in flight
859 *
James Feistb4383f42018-08-06 16:54:10 -0700860 if (!validateJson(schema, data))
861 {
862 std::cerr << "Error validating " << jsonPath.string() << "\n";
863 continue;
864 }
James Feist8da99192019-01-24 08:20:16 -0800865 */
James Feistb4383f42018-08-06 16:54:10 -0700866
James Feist3cb5fec2018-01-23 14:41:51 -0800867 if (data.type() == nlohmann::json::value_t::array)
868 {
James Feista465ccc2019-02-08 12:51:01 -0800869 for (auto& d : data)
James Feist3cb5fec2018-01-23 14:41:51 -0800870 {
871 configurations.emplace_back(d);
872 }
873 }
874 else
875 {
876 configurations.emplace_back(data);
877 }
878 }
Ed Tanous072e25d2018-12-16 21:45:20 -0800879 return true;
James Feist75fdeeb2018-02-20 14:26:16 -0800880}
James Feist3cb5fec2018-01-23 14:41:51 -0800881
Andrew Jeffery55192932022-03-24 12:29:27 +1030882static bool deviceRequiresPowerOn(const nlohmann::json& entity)
883{
884 auto powerState = entity.find("PowerState");
Andrew Jefferyb6209442022-03-24 12:36:20 +1030885 if (powerState == entity.end())
Andrew Jeffery55192932022-03-24 12:29:27 +1030886 {
Andrew Jefferyb6209442022-03-24 12:36:20 +1030887 return false;
Andrew Jeffery55192932022-03-24 12:29:27 +1030888 }
889
Ed Tanous3013fb42022-07-09 08:27:06 -0700890 const auto* ptr = powerState->get_ptr<const std::string*>();
891 if (ptr == nullptr)
Andrew Jefferyb6209442022-03-24 12:36:20 +1030892 {
893 return false;
894 }
895
896 return *ptr == "On" || *ptr == "BiosPost";
Andrew Jeffery55192932022-03-24 12:29:27 +1030897}
898
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030899static void pruneDevice(const nlohmann::json& systemConfiguration,
900 const bool powerOff, const bool scannedPowerOff,
901 const std::string& name, const nlohmann::json& device)
902{
903 if (systemConfiguration.contains(name))
904 {
905 return;
906 }
907
Andrew Jeffery4db38bc2022-03-24 13:42:41 +1030908 if (deviceRequiresPowerOn(device) && (powerOff || scannedPowerOff))
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030909 {
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030910 return;
911 }
912
913 logDeviceRemoved(device);
914}
915
James Feistb1728ca2020-04-30 15:40:55 -0700916void startRemovedTimer(boost::asio::steady_timer& timer,
James Feist1df06a42019-04-11 14:23:04 -0700917 nlohmann::json& systemConfiguration)
918{
919 static bool scannedPowerOff = false;
920 static bool scannedPowerOn = false;
921
James Feistfb00f392019-06-25 14:16:48 -0700922 if (systemConfiguration.empty() || lastJson.empty())
923 {
924 return; // not ready yet
925 }
James Feist1df06a42019-04-11 14:23:04 -0700926 if (scannedPowerOn)
927 {
928 return;
929 }
930
931 if (!isPowerOn() && scannedPowerOff)
932 {
933 return;
934 }
935
James Feistb1728ca2020-04-30 15:40:55 -0700936 timer.expires_after(std::chrono::seconds(10));
Andrew Jeffery27a1cfb2022-03-24 12:31:53 +1030937 timer.async_wait(
938 [&systemConfiguration](const boost::system::error_code& ec) {
Patrick Williamsdf190612023-05-10 07:51:34 -0500939 if (ec == boost::asio::error::operation_aborted)
940 {
941 return;
942 }
Andrew Jeffery27a1cfb2022-03-24 12:31:53 +1030943
Patrick Williamsdf190612023-05-10 07:51:34 -0500944 bool powerOff = !isPowerOn();
945 for (const auto& [name, device] : lastJson.items())
946 {
947 pruneDevice(systemConfiguration, powerOff, scannedPowerOff, name,
948 device);
949 }
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030950
Patrick Williamsdf190612023-05-10 07:51:34 -0500951 scannedPowerOff = true;
952 if (!powerOff)
953 {
954 scannedPowerOn = true;
955 }
956 });
James Feist1df06a42019-04-11 14:23:04 -0700957}
958
Andrew Jeffery2f750d22022-03-24 14:32:57 +1030959static std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>&
960 getDeviceInterfaces(const nlohmann::json& device)
961{
962 return inventory[device["Name"].get<std::string>()];
963}
964
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030965static void pruneConfiguration(nlohmann::json& systemConfiguration,
966 sdbusplus::asio::object_server& objServer,
967 bool powerOff, const std::string& name,
968 const nlohmann::json& device)
969{
970 if (powerOff && deviceRequiresPowerOn(device))
971 {
972 // power not on yet, don't know if it's there or not
973 return;
974 }
975
976 auto& ifaces = getDeviceInterfaces(device);
977 for (auto& iface : ifaces)
978 {
979 auto sharedPtr = iface.lock();
980 if (!!sharedPtr)
981 {
982 objServer.remove_interface(sharedPtr);
983 }
984 }
985
986 ifaces.clear();
987 systemConfiguration.erase(name);
Matt Spinler6eb60972023-08-14 16:36:20 -0500988 topology.remove(device["Name"].get<std::string>());
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030989 logDeviceRemoved(device);
990}
991
Andrew Jeffery7c4cbbe2022-03-24 15:27:10 +1030992static void deriveNewConfiguration(const nlohmann::json& oldConfiguration,
993 nlohmann::json& newConfiguration)
994{
995 for (auto it = newConfiguration.begin(); it != newConfiguration.end();)
996 {
997 auto findKey = oldConfiguration.find(it.key());
998 if (findKey != oldConfiguration.end())
999 {
1000 it = newConfiguration.erase(it);
1001 }
1002 else
1003 {
1004 it++;
1005 }
1006 }
1007}
1008
Andrew Jefferye35d0ac2022-03-24 15:53:13 +10301009static void publishNewConfiguration(
1010 const size_t& instance, const size_t count,
1011 boost::asio::steady_timer& timer, nlohmann::json& systemConfiguration,
1012 // Gerrit discussion:
1013 // https://gerrit.openbmc-project.xyz/c/openbmc/entity-manager/+/52316/6
1014 //
1015 // Discord discussion:
1016 // https://discord.com/channels/775381525260664832/867820390406422538/958048437729910854
1017 //
1018 // NOLINTNEXTLINE(performance-unnecessary-value-param)
1019 const nlohmann::json newConfiguration,
1020 sdbusplus::asio::object_server& objServer)
1021{
1022 loadOverlays(newConfiguration);
1023
Ed Tanous49a888c2023-03-06 13:44:51 -08001024 boost::asio::post(io, [systemConfiguration]() {
Andrew Jefferye35d0ac2022-03-24 15:53:13 +10301025 if (!writeJsonFiles(systemConfiguration))
1026 {
1027 std::cerr << "Error writing json files\n";
1028 }
1029 });
1030
Ed Tanous49a888c2023-03-06 13:44:51 -08001031 boost::asio::post(io, [&instance, count, &timer, newConfiguration,
1032 &systemConfiguration, &objServer]() {
Andrew Jefferye35d0ac2022-03-24 15:53:13 +10301033 postToDbus(newConfiguration, systemConfiguration, objServer);
1034 if (count == instance)
1035 {
1036 startRemovedTimer(timer, systemConfiguration);
1037 }
1038 });
1039}
1040
James Feist8f2710a2018-05-09 17:18:55 -07001041// main properties changed entry
James Feist4dc617b2020-05-01 09:54:47 -07001042void propertiesChangedCallback(nlohmann::json& systemConfiguration,
1043 sdbusplus::asio::object_server& objServer)
James Feist8f2710a2018-05-09 17:18:55 -07001044{
James Feist2539ccd2020-05-01 16:15:08 -07001045 static bool inProgress = false;
James Feistb1728ca2020-04-30 15:40:55 -07001046 static boost::asio::steady_timer timer(io);
James Feist899e17f2019-09-13 11:46:29 -07001047 static size_t instance = 0;
1048 instance++;
1049 size_t count = instance;
James Feist1df06a42019-04-11 14:23:04 -07001050
James Feistb1728ca2020-04-30 15:40:55 -07001051 timer.expires_after(std::chrono::seconds(5));
James Feist8f2710a2018-05-09 17:18:55 -07001052
1053 // setup an async wait as we normally get flooded with new requests
James Feist4dc617b2020-05-01 09:54:47 -07001054 timer.async_wait([&systemConfiguration, &objServer,
James Feist899e17f2019-09-13 11:46:29 -07001055 count](const boost::system::error_code& ec) {
James Feist8f2710a2018-05-09 17:18:55 -07001056 if (ec == boost::asio::error::operation_aborted)
1057 {
1058 // we were cancelled
1059 return;
1060 }
Ed Tanous07d467b2021-02-23 14:48:37 -08001061 if (ec)
James Feist8f2710a2018-05-09 17:18:55 -07001062 {
1063 std::cerr << "async wait error " << ec << "\n";
1064 return;
1065 }
1066
James Feist2539ccd2020-05-01 16:15:08 -07001067 if (inProgress)
1068 {
1069 propertiesChangedCallback(systemConfiguration, objServer);
1070 return;
1071 }
1072 inProgress = true;
1073
James Feist8f2710a2018-05-09 17:18:55 -07001074 nlohmann::json oldConfiguration = systemConfiguration;
James Feist899e17f2019-09-13 11:46:29 -07001075 auto missingConfigurations = std::make_shared<nlohmann::json>();
1076 *missingConfigurations = systemConfiguration;
1077
James Feist8f2710a2018-05-09 17:18:55 -07001078 std::list<nlohmann::json> configurations;
Andrew Jefferyf3311792022-03-29 22:09:00 +10301079 if (!loadConfigurations(configurations))
James Feist8f2710a2018-05-09 17:18:55 -07001080 {
Andrew Jefferyf3311792022-03-29 22:09:00 +10301081 std::cerr << "Could not load configurations\n";
James Feist2539ccd2020-05-01 16:15:08 -07001082 inProgress = false;
James Feist8f2710a2018-05-09 17:18:55 -07001083 return;
1084 }
1085
1086 auto perfScan = std::make_shared<PerformScan>(
James Feist899e17f2019-09-13 11:46:29 -07001087 systemConfiguration, *missingConfigurations, configurations,
James Feist4dc617b2020-05-01 09:54:47 -07001088 objServer,
1089 [&systemConfiguration, &objServer, count, oldConfiguration,
1090 missingConfigurations]() {
Patrick Williamsdf190612023-05-10 07:51:34 -05001091 // this is something that since ac has been applied to the bmc
1092 // we saw, and we no longer see it
1093 bool powerOff = !isPowerOn();
1094 for (const auto& [name, device] : missingConfigurations->items())
1095 {
1096 pruneConfiguration(systemConfiguration, objServer, powerOff,
1097 name, device);
1098 }
James Feist899e17f2019-09-13 11:46:29 -07001099
Patrick Williamsdf190612023-05-10 07:51:34 -05001100 nlohmann::json newConfiguration = systemConfiguration;
Andrew Jeffery7c4cbbe2022-03-24 15:27:10 +10301101
Patrick Williamsdf190612023-05-10 07:51:34 -05001102 deriveNewConfiguration(oldConfiguration, newConfiguration);
Andrew Jeffery7c4cbbe2022-03-24 15:27:10 +10301103
Patrick Williamsdf190612023-05-10 07:51:34 -05001104 for (const auto& [_, device] : newConfiguration.items())
1105 {
1106 logDeviceAdded(device);
1107 }
James Feist899e17f2019-09-13 11:46:29 -07001108
Patrick Williamsdf190612023-05-10 07:51:34 -05001109 inProgress = false;
James Feist2539ccd2020-05-01 16:15:08 -07001110
Patrick Williamsdf190612023-05-10 07:51:34 -05001111 boost::asio::post(
1112 io, std::bind_front(publishNewConfiguration, std::ref(instance),
1113 count, std::ref(timer),
1114 std::ref(systemConfiguration),
1115 newConfiguration, std::ref(objServer)));
Patrick Williamsb9dd7f82023-10-20 11:20:11 -05001116 });
James Feist8f2710a2018-05-09 17:18:55 -07001117 perfScan->run();
1118 });
James Feist75fdeeb2018-02-20 14:26:16 -08001119}
1120
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001121// Extract the D-Bus interfaces to probe from the JSON config files.
1122static std::set<std::string> getProbeInterfaces()
1123{
1124 std::set<std::string> interfaces;
1125 std::list<nlohmann::json> configurations;
1126 if (!loadConfigurations(configurations))
1127 {
1128 return interfaces;
1129 }
1130
1131 for (auto it = configurations.begin(); it != configurations.end();)
1132 {
1133 auto findProbe = it->find("Probe");
1134 if (findProbe == it->end())
1135 {
1136 std::cerr << "configuration file missing probe:\n " << *it << "\n";
1137 it++;
1138 continue;
1139 }
1140
1141 nlohmann::json probeCommand;
1142 if ((*findProbe).type() != nlohmann::json::value_t::array)
1143 {
1144 probeCommand = nlohmann::json::array();
1145 probeCommand.push_back(*findProbe);
1146 }
1147 else
1148 {
1149 probeCommand = *findProbe;
1150 }
1151
1152 for (const nlohmann::json& probeJson : probeCommand)
1153 {
1154 const std::string* probe = probeJson.get_ptr<const std::string*>();
1155 if (probe == nullptr)
1156 {
1157 std::cerr << "Probe statement wasn't a string, can't parse";
1158 continue;
1159 }
1160 // Skip it if the probe cmd doesn't contain an interface.
1161 if (findProbeType(*probe))
1162 {
1163 continue;
1164 }
1165
1166 // syntax requires probe before first open brace
1167 auto findStart = probe->find('(');
1168 if (findStart != std::string::npos)
1169 {
1170 std::string interface = probe->substr(0, findStart);
1171 interfaces.emplace(interface);
1172 }
1173 }
1174 it++;
1175 }
1176
1177 return interfaces;
1178}
1179
1180// Check if InterfacesAdded payload contains an iface that needs probing.
1181static bool
1182 iaContainsProbeInterface(sdbusplus::message_t& msg,
1183 const std::set<std::string>& probeInterfaces)
1184{
1185 sdbusplus::message::object_path path;
1186 DBusObject interfaces;
1187 std::set<std::string> interfaceSet;
1188 std::set<std::string> intersect;
1189
1190 msg.read(path, interfaces);
1191
1192 std::for_each(interfaces.begin(), interfaces.end(),
1193 [&interfaceSet](const auto& iface) {
Patrick Williamsdf190612023-05-10 07:51:34 -05001194 interfaceSet.insert(iface.first);
1195 });
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001196
1197 std::set_intersection(interfaceSet.begin(), interfaceSet.end(),
1198 probeInterfaces.begin(), probeInterfaces.end(),
1199 std::inserter(intersect, intersect.end()));
1200 return !intersect.empty();
1201}
1202
1203// Check if InterfacesRemoved payload contains an iface that needs probing.
1204static bool
1205 irContainsProbeInterface(sdbusplus::message_t& msg,
1206 const std::set<std::string>& probeInterfaces)
1207{
1208 sdbusplus::message::object_path path;
1209 std::set<std::string> interfaces;
1210 std::set<std::string> intersect;
1211
1212 msg.read(path, interfaces);
1213
1214 std::set_intersection(interfaces.begin(), interfaces.end(),
1215 probeInterfaces.begin(), probeInterfaces.end(),
1216 std::inserter(intersect, intersect.end()));
1217 return !intersect.empty();
1218}
1219
James Feist98132792019-07-09 13:29:09 -07001220int main()
James Feist75fdeeb2018-02-20 14:26:16 -08001221{
1222 // setup connection to dbus
Ed Tanous07d467b2021-02-23 14:48:37 -08001223 systemBus = std::make_shared<sdbusplus::asio::connection>(io);
1224 systemBus->request_name("xyz.openbmc_project.EntityManager");
James Feist4131aea2018-03-09 09:47:30 -08001225
Nan Zhoua3315672022-09-20 19:48:14 +00001226 // The EntityManager object itself doesn't expose any properties.
1227 // No need to set up ObjectManager for the |EntityManager| object.
1228 sdbusplus::asio::object_server objServer(systemBus, /*skipManager=*/true);
1229
1230 // All other objects that EntityManager currently support are under the
1231 // inventory subtree.
1232 // See the discussion at
1233 // https://discord.com/channels/775381525260664832/1018929092009144380
1234 objServer.add_manager("/xyz/openbmc_project/inventory");
James Feistfd1264a2018-05-03 12:10:00 -07001235
James Feist8f2710a2018-05-09 17:18:55 -07001236 std::shared_ptr<sdbusplus::asio::dbus_interface> entityIface =
1237 objServer.add_interface("/xyz/openbmc_project/EntityManager",
1238 "xyz.openbmc_project.EntityManager");
James Feistfd1264a2018-05-03 12:10:00 -07001239
James Feist4131aea2018-03-09 09:47:30 -08001240 // to keep reference to the match / filter objects so they don't get
1241 // destroyed
James Feist8f2710a2018-05-09 17:18:55 -07001242
1243 nlohmann::json systemConfiguration = nlohmann::json::object();
1244
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001245 std::set<std::string> probeInterfaces = getProbeInterfaces();
1246
Brad Bishopc76af0f2020-12-04 13:50:23 -05001247 // We need a poke from DBus for static providers that create all their
1248 // objects prior to claiming a well-known name, and thus don't emit any
1249 // org.freedesktop.DBus.Properties signals. Similarly if a process exits
1250 // for any reason, expected or otherwise, we'll need a poke to remove
1251 // entities from DBus.
Patrick Williams2af39222022-07-22 19:26:56 -05001252 sdbusplus::bus::match_t nameOwnerChangedMatch(
1253 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishopc76af0f2020-12-04 13:50:23 -05001254 sdbusplus::bus::match::rules::nameOwnerChanged(),
Patrick Williams7b8786f2022-10-10 10:23:37 -05001255 [&](sdbusplus::message_t& m) {
Patrick Williamsdf190612023-05-10 07:51:34 -05001256 auto [name, oldOwner,
1257 newOwner] = m.unpack<std::string, std::string, std::string>();
Patrick Williams7b8786f2022-10-10 10:23:37 -05001258
Patrick Williamsdf190612023-05-10 07:51:34 -05001259 if (name.starts_with(':'))
1260 {
1261 // We should do nothing with unique-name connections.
1262 return;
1263 }
Patrick Williams7b8786f2022-10-10 10:23:37 -05001264
Patrick Williamsdf190612023-05-10 07:51:34 -05001265 propertiesChangedCallback(systemConfiguration, objServer);
Patrick Williamsb9dd7f82023-10-20 11:20:11 -05001266 });
Brad Bishop10a8c5f2020-12-07 21:40:07 -05001267 // We also need a poke from DBus when new interfaces are created or
1268 // destroyed.
Patrick Williams2af39222022-07-22 19:26:56 -05001269 sdbusplus::bus::match_t interfacesAddedMatch(
1270 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishop10a8c5f2020-12-07 21:40:07 -05001271 sdbusplus::bus::match::rules::interfacesAdded(),
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001272 [&](sdbusplus::message_t& msg) {
Patrick Williamsdf190612023-05-10 07:51:34 -05001273 if (iaContainsProbeInterface(msg, probeInterfaces))
1274 {
1275 propertiesChangedCallback(systemConfiguration, objServer);
1276 }
Patrick Williamsb9dd7f82023-10-20 11:20:11 -05001277 });
Patrick Williams2af39222022-07-22 19:26:56 -05001278 sdbusplus::bus::match_t interfacesRemovedMatch(
1279 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishop10a8c5f2020-12-07 21:40:07 -05001280 sdbusplus::bus::match::rules::interfacesRemoved(),
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001281 [&](sdbusplus::message_t& msg) {
Patrick Williamsdf190612023-05-10 07:51:34 -05001282 if (irContainsProbeInterface(msg, probeInterfaces))
1283 {
1284 propertiesChangedCallback(systemConfiguration, objServer);
1285 }
Patrick Williamsb9dd7f82023-10-20 11:20:11 -05001286 });
Brad Bishopc76af0f2020-12-04 13:50:23 -05001287
Ed Tanous49a888c2023-03-06 13:44:51 -08001288 boost::asio::post(io, [&]() {
1289 propertiesChangedCallback(systemConfiguration, objServer);
1290 });
James Feist4131aea2018-03-09 09:47:30 -08001291
James Feistfd1264a2018-05-03 12:10:00 -07001292 entityIface->register_method("ReScan", [&]() {
James Feist4dc617b2020-05-01 09:54:47 -07001293 propertiesChangedCallback(systemConfiguration, objServer);
James Feist75fdeeb2018-02-20 14:26:16 -08001294 });
James Feist8f2710a2018-05-09 17:18:55 -07001295 entityIface->initialize();
1296
James Feist1df06a42019-04-11 14:23:04 -07001297 if (fwVersionIsSame())
1298 {
1299 if (std::filesystem::is_regular_file(currentConfiguration))
1300 {
1301 // this file could just be deleted, but it's nice for debug
1302 std::filesystem::create_directory(tempConfigDir);
1303 std::filesystem::remove(lastConfiguration);
1304 std::filesystem::copy(currentConfiguration, lastConfiguration);
1305 std::filesystem::remove(currentConfiguration);
1306
1307 std::ifstream jsonStream(lastConfiguration);
1308 if (jsonStream.good())
1309 {
1310 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
1311 if (data.is_discarded())
1312 {
1313 std::cerr << "syntax error in " << lastConfiguration
1314 << "\n";
1315 }
1316 else
1317 {
1318 lastJson = std::move(data);
1319 }
1320 }
1321 else
1322 {
1323 std::cerr << "unable to open " << lastConfiguration << "\n";
1324 }
1325 }
1326 }
1327 else
1328 {
1329 // not an error, just logging at this level to make it in the journal
1330 std::cerr << "Clearing previous configuration\n";
1331 std::filesystem::remove(currentConfiguration);
1332 }
1333
1334 // some boards only show up after power is on, we want to not say they are
1335 // removed until the same state happens
Ed Tanous07d467b2021-02-23 14:48:37 -08001336 setupPowerMatch(systemBus);
James Feist1df06a42019-04-11 14:23:04 -07001337
James Feist1b2e2242018-01-30 13:45:19 -08001338 io.run();
James Feist3cb5fec2018-01-23 14:41:51 -08001339
1340 return 0;
James Feist75fdeeb2018-02-20 14:26:16 -08001341}