blob: ee08c00abcc6805cff713e29f654d77fde30ee61 [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;
James Feist3cb5fec2018-01-23 14:41:51 -080079
James Feist02d2b932020-02-06 16:28:48 -080080boost::asio::io_context io;
81
Ed Tanous07d467b2021-02-23 14:48:37 -080082const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]");
83const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]");
James Feist1b2e2242018-01-30 13:45:19 -080084
Andrew Jeffery666583b2021-12-01 15:50:12 +103085FoundProbeTypeT findProbeType(const std::string& probe)
86{
87 boost::container::flat_map<const char*, probe_type_codes,
88 CmpStr>::const_iterator probeType;
89 for (probeType = probeTypes.begin(); probeType != probeTypes.end();
90 ++probeType)
91 {
92 if (probe.find(probeType->first) != std::string::npos)
93 {
94 return probeType;
95 }
96 }
97
98 return std::nullopt;
99}
100
James Feistd58879a2019-09-11 11:26:07 -0700101static std::shared_ptr<sdbusplus::asio::dbus_interface>
102 createInterface(sdbusplus::asio::object_server& objServer,
103 const std::string& path, const std::string& interface,
James Feist02d2b932020-02-06 16:28:48 -0800104 const std::string& parent, bool checkNull = false)
James Feistd58879a2019-09-11 11:26:07 -0700105{
James Feist02d2b932020-02-06 16:28:48 -0800106 // on first add we have no reason to check for null before add, as there
107 // won't be any. For dynamically added interfaces, we check for null so that
108 // a constant delete/add will not create a memory leak
109
110 auto ptr = objServer.add_interface(path, interface);
111 auto& dataVector = inventory[parent];
112 if (checkNull)
113 {
114 auto it = std::find_if(dataVector.begin(), dataVector.end(),
115 [](const auto& p) { return p.expired(); });
116 if (it != dataVector.end())
117 {
118 *it = ptr;
119 return ptr;
120 }
121 }
122 dataVector.emplace_back(ptr);
123 return ptr;
James Feistd58879a2019-09-11 11:26:07 -0700124}
125
James Feist8f2710a2018-05-09 17:18:55 -0700126// writes output files to persist data
James Feista465ccc2019-02-08 12:51:01 -0800127bool writeJsonFiles(const nlohmann::json& systemConfiguration)
James Feist1b2e2242018-01-30 13:45:19 -0800128{
James Feist1df06a42019-04-11 14:23:04 -0700129 std::filesystem::create_directory(configurationOutDir);
130 std::ofstream output(currentConfiguration);
James Feistbb43d022018-06-12 15:44:33 -0700131 if (!output.good())
132 {
133 return false;
134 }
James Feist1b2e2242018-01-30 13:45:19 -0800135 output << systemConfiguration.dump(4);
136 output.close();
James Feistbb43d022018-06-12 15:44:33 -0700137 return true;
James Feist8f2710a2018-05-09 17:18:55 -0700138}
James Feist1b2e2242018-01-30 13:45:19 -0800139
James Feist97a63f12018-05-17 13:50:57 -0700140template <typename JsonType>
James Feista465ccc2019-02-08 12:51:01 -0800141bool setJsonFromPointer(const std::string& ptrStr, const JsonType& value,
142 nlohmann::json& systemConfiguration)
James Feist97a63f12018-05-17 13:50:57 -0700143{
144 try
145 {
146 nlohmann::json::json_pointer ptr(ptrStr);
James Feista465ccc2019-02-08 12:51:01 -0800147 nlohmann::json& ref = systemConfiguration[ptr];
James Feist97a63f12018-05-17 13:50:57 -0700148 ref = value;
149 return true;
150 }
James Feist98132792019-07-09 13:29:09 -0700151 catch (const std::out_of_range&)
James Feist97a63f12018-05-17 13:50:57 -0700152 {
153 return false;
154 }
155}
James Feistbb43d022018-06-12 15:44:33 -0700156
James Feistebcc26b2019-03-22 12:30:43 -0700157// template function to add array as dbus property
158template <typename PropertyType>
159void addArrayToDbus(const std::string& name, const nlohmann::json& array,
160 sdbusplus::asio::dbus_interface* iface,
161 sdbusplus::asio::PropertyPermission permission,
162 nlohmann::json& systemConfiguration,
163 const std::string& jsonPointerString)
164{
165 std::vector<PropertyType> values;
166 for (const auto& property : array)
167 {
168 auto ptr = property.get_ptr<const PropertyType*>();
169 if (ptr != nullptr)
170 {
171 values.emplace_back(*ptr);
172 }
173 }
174
175 if (permission == sdbusplus::asio::PropertyPermission::readOnly)
176 {
177 iface->register_property(name, values);
178 }
179 else
180 {
181 iface->register_property(
182 name, values,
183 [&systemConfiguration,
184 jsonPointerString{std::string(jsonPointerString)}](
185 const std::vector<PropertyType>& newVal,
186 std::vector<PropertyType>& val) {
Patrick Williamsdf190612023-05-10 07:51:34 -0500187 val = newVal;
188 if (!setJsonFromPointer(jsonPointerString, val,
189 systemConfiguration))
190 {
191 std::cerr << "error setting json field\n";
192 return -1;
193 }
194 if (!writeJsonFiles(systemConfiguration))
195 {
196 std::cerr << "error setting json file\n";
197 return -1;
198 }
199 return 1;
James Feistebcc26b2019-03-22 12:30:43 -0700200 });
201 }
202}
203
James Feistbb43d022018-06-12 15:44:33 -0700204template <typename PropertyType>
Ed Tanous3013fb42022-07-09 08:27:06 -0700205void addProperty(const std::string& name, const PropertyType& value,
James Feista465ccc2019-02-08 12:51:01 -0800206 sdbusplus::asio::dbus_interface* iface,
207 nlohmann::json& systemConfiguration,
208 const std::string& jsonPointerString,
James Feistbb43d022018-06-12 15:44:33 -0700209 sdbusplus::asio::PropertyPermission permission)
210{
211 if (permission == sdbusplus::asio::PropertyPermission::readOnly)
212 {
Ed Tanous3013fb42022-07-09 08:27:06 -0700213 iface->register_property(name, value);
James Feistbb43d022018-06-12 15:44:33 -0700214 return;
215 }
James Feist68500ff2018-08-08 15:40:42 -0700216 iface->register_property(
Ed Tanous3013fb42022-07-09 08:27:06 -0700217 name, value,
James Feist68500ff2018-08-08 15:40:42 -0700218 [&systemConfiguration,
219 jsonPointerString{std::string(jsonPointerString)}](
James Feista465ccc2019-02-08 12:51:01 -0800220 const PropertyType& newVal, PropertyType& val) {
Patrick Williamsdf190612023-05-10 07:51:34 -0500221 val = newVal;
222 if (!setJsonFromPointer(jsonPointerString, val, systemConfiguration))
223 {
224 std::cerr << "error setting json field\n";
225 return -1;
226 }
227 if (!writeJsonFiles(systemConfiguration))
228 {
229 std::cerr << "error setting json file\n";
230 return -1;
231 }
232 return 1;
James Feistc6248a52018-08-14 10:09:45 -0700233 });
234}
235
236void createDeleteObjectMethod(
James Feista465ccc2019-02-08 12:51:01 -0800237 const std::string& jsonPointerPath,
238 const std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
239 sdbusplus::asio::object_server& objServer,
240 nlohmann::json& systemConfiguration)
James Feistc6248a52018-08-14 10:09:45 -0700241{
242 std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface;
Patrick Williamsdf190612023-05-10 07:51:34 -0500243 iface->register_method("Delete",
244 [&objServer, &systemConfiguration, interface,
245 jsonPointerPath{std::string(jsonPointerPath)}]() {
246 std::shared_ptr<sdbusplus::asio::dbus_interface> dbusInterface =
247 interface.lock();
248 if (!dbusInterface)
249 {
250 // this technically can't happen as the pointer is pointing to
251 // us
252 throw DBusInternalError();
253 }
254 nlohmann::json::json_pointer ptr(jsonPointerPath);
255 systemConfiguration[ptr] = nullptr;
James Feistc6248a52018-08-14 10:09:45 -0700256
Patrick Williamsdf190612023-05-10 07:51:34 -0500257 // todo(james): dig through sdbusplus to find out why we can't
258 // delete it in a method call
259 boost::asio::post(io, [&objServer, dbusInterface]() mutable {
260 objServer.remove_interface(dbusInterface);
James Feist68500ff2018-08-08 15:40:42 -0700261 });
Patrick Williamsdf190612023-05-10 07:51:34 -0500262
263 if (!writeJsonFiles(systemConfiguration))
264 {
265 std::cerr << "error setting json file\n";
266 throw DBusInternalError();
267 }
268 });
James Feistbb43d022018-06-12 15:44:33 -0700269}
270
James Feist1b2e2242018-01-30 13:45:19 -0800271// adds simple json types to interface's properties
James Feistbb43d022018-06-12 15:44:33 -0700272void populateInterfaceFromJson(
James Feista465ccc2019-02-08 12:51:01 -0800273 nlohmann::json& systemConfiguration, const std::string& jsonPointerPath,
274 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
275 nlohmann::json& dict, sdbusplus::asio::object_server& objServer,
James Feistbb43d022018-06-12 15:44:33 -0700276 sdbusplus::asio::PropertyPermission permission =
277 sdbusplus::asio::PropertyPermission::readOnly)
James Feist1b2e2242018-01-30 13:45:19 -0800278{
Patrick Williams2594d362022-09-28 06:46:24 -0500279 for (const auto& [key, value] : dict.items())
James Feist1b2e2242018-01-30 13:45:19 -0800280 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030281 auto type = value.type();
James Feist8f2710a2018-05-09 17:18:55 -0700282 bool array = false;
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030283 if (value.type() == nlohmann::json::value_t::array)
James Feist8f2710a2018-05-09 17:18:55 -0700284 {
285 array = true;
Ed Tanous3013fb42022-07-09 08:27:06 -0700286 if (value.empty())
James Feist8f2710a2018-05-09 17:18:55 -0700287 {
288 continue;
289 }
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030290 type = value[0].type();
James Feist8f2710a2018-05-09 17:18:55 -0700291 bool isLegal = true;
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030292 for (const auto& arrayItem : value)
James Feist8f2710a2018-05-09 17:18:55 -0700293 {
294 if (arrayItem.type() != type)
295 {
296 isLegal = false;
297 break;
298 }
299 }
300 if (!isLegal)
301 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030302 std::cerr << "dbus format error" << value << "\n";
James Feist8f2710a2018-05-09 17:18:55 -0700303 continue;
304 }
James Feista218ddb2019-04-11 14:01:31 -0700305 }
306 if (type == nlohmann::json::value_t::object)
307 {
308 continue; // handled elsewhere
James Feist8f2710a2018-05-09 17:18:55 -0700309 }
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030310
311 std::string path = jsonPointerPath;
312 path.append("/").append(key);
James Feistbb43d022018-06-12 15:44:33 -0700313 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
314 {
315 // all setable numbers are doubles as it is difficult to always
316 // create a configuration file with all whole numbers as decimals
317 // i.e. 1.0
James Feistebcc26b2019-03-22 12:30:43 -0700318 if (array)
319 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030320 if (value[0].is_number())
James Feistebcc26b2019-03-22 12:30:43 -0700321 {
322 type = nlohmann::json::value_t::number_float;
323 }
324 }
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030325 else if (value.is_number())
James Feistbb43d022018-06-12 15:44:33 -0700326 {
327 type = nlohmann::json::value_t::number_float;
328 }
329 }
330
James Feist8f2710a2018-05-09 17:18:55 -0700331 switch (type)
James Feist1b2e2242018-01-30 13:45:19 -0800332 {
James Feist9eb0b582018-04-27 12:15:46 -0700333 case (nlohmann::json::value_t::boolean):
334 {
James Feist8f2710a2018-05-09 17:18:55 -0700335 if (array)
336 {
337 // todo: array of bool isn't detected correctly by
338 // sdbusplus, change it to numbers
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030339 addArrayToDbus<uint64_t>(key, value, iface.get(),
340 permission, systemConfiguration,
341 path);
James Feist8f2710a2018-05-09 17:18:55 -0700342 }
James Feistbb43d022018-06-12 15:44:33 -0700343
James Feist97a63f12018-05-17 13:50:57 -0700344 else
345 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030346 addProperty(key, value.get<bool>(), iface.get(),
347 systemConfiguration, path, permission);
James Feist97a63f12018-05-17 13:50:57 -0700348 }
James Feist9eb0b582018-04-27 12:15:46 -0700349 break;
350 }
351 case (nlohmann::json::value_t::number_integer):
352 {
James Feist8f2710a2018-05-09 17:18:55 -0700353 if (array)
354 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030355 addArrayToDbus<int64_t>(key, value, iface.get(), permission,
Andrew Jeffery029ee282022-03-25 13:11:36 +1030356 systemConfiguration, path);
James Feist97a63f12018-05-17 13:50:57 -0700357 }
358 else
359 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030360 addProperty(key, value.get<int64_t>(), iface.get(),
361 systemConfiguration, path,
James Feistbb43d022018-06-12 15:44:33 -0700362 sdbusplus::asio::PropertyPermission::readOnly);
James Feist97a63f12018-05-17 13:50:57 -0700363 }
James Feist9eb0b582018-04-27 12:15:46 -0700364 break;
365 }
366 case (nlohmann::json::value_t::number_unsigned):
367 {
James Feist8f2710a2018-05-09 17:18:55 -0700368 if (array)
369 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030370 addArrayToDbus<uint64_t>(key, value, iface.get(),
371 permission, systemConfiguration,
372 path);
James Feist97a63f12018-05-17 13:50:57 -0700373 }
374 else
375 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030376 addProperty(key, value.get<uint64_t>(), iface.get(),
Andrew Jeffery029ee282022-03-25 13:11:36 +1030377 systemConfiguration, path,
James Feistbb43d022018-06-12 15:44:33 -0700378 sdbusplus::asio::PropertyPermission::readOnly);
James Feist97a63f12018-05-17 13:50:57 -0700379 }
James Feist9eb0b582018-04-27 12:15:46 -0700380 break;
381 }
382 case (nlohmann::json::value_t::number_float):
383 {
James Feist8f2710a2018-05-09 17:18:55 -0700384 if (array)
385 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030386 addArrayToDbus<double>(key, value, iface.get(), permission,
Andrew Jeffery029ee282022-03-25 13:11:36 +1030387 systemConfiguration, path);
James Feist8f2710a2018-05-09 17:18:55 -0700388 }
James Feistbb43d022018-06-12 15:44:33 -0700389
James Feist97a63f12018-05-17 13:50:57 -0700390 else
391 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030392 addProperty(key, value.get<double>(), iface.get(),
393 systemConfiguration, path, permission);
James Feist97a63f12018-05-17 13:50:57 -0700394 }
James Feist9eb0b582018-04-27 12:15:46 -0700395 break;
396 }
397 case (nlohmann::json::value_t::string):
398 {
James Feist8f2710a2018-05-09 17:18:55 -0700399 if (array)
400 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030401 addArrayToDbus<std::string>(key, value, iface.get(),
402 permission, systemConfiguration,
403 path);
James Feist97a63f12018-05-17 13:50:57 -0700404 }
405 else
406 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030407 addProperty(key, value.get<std::string>(), iface.get(),
408 systemConfiguration, path, permission);
James Feist97a63f12018-05-17 13:50:57 -0700409 }
James Feist9eb0b582018-04-27 12:15:46 -0700410 break;
411 }
James Feist0eb40352019-04-09 14:44:04 -0700412 default:
413 {
James Feista218ddb2019-04-11 14:01:31 -0700414 std::cerr << "Unexpected json type in system configuration "
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030415 << key << ": " << value.type_name() << "\n";
James Feist0eb40352019-04-09 14:44:04 -0700416 break;
417 }
James Feist1b2e2242018-01-30 13:45:19 -0800418 }
419 }
James Feistc6248a52018-08-14 10:09:45 -0700420 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
421 {
422 createDeleteObjectMethod(jsonPointerPath, iface, objServer,
423 systemConfiguration);
424 }
James Feist8f2710a2018-05-09 17:18:55 -0700425 iface->initialize();
James Feist1b2e2242018-01-30 13:45:19 -0800426}
427
James Feista465ccc2019-02-08 12:51:01 -0800428sdbusplus::asio::PropertyPermission getPermission(const std::string& interface)
James Feistc6248a52018-08-14 10:09:45 -0700429{
430 return std::find(settableInterfaces.begin(), settableInterfaces.end(),
431 interface) != settableInterfaces.end()
432 ? sdbusplus::asio::PropertyPermission::readWrite
433 : sdbusplus::asio::PropertyPermission::readOnly;
434}
435
James Feista465ccc2019-02-08 12:51:01 -0800436void createAddObjectMethod(const std::string& jsonPointerPath,
437 const std::string& path,
438 nlohmann::json& systemConfiguration,
James Feistd58879a2019-09-11 11:26:07 -0700439 sdbusplus::asio::object_server& objServer,
440 const std::string& board)
James Feist68500ff2018-08-08 15:40:42 -0700441{
James Feistd58879a2019-09-11 11:26:07 -0700442 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = createInterface(
443 objServer, path, "xyz.openbmc_project.AddObject", board);
James Feist68500ff2018-08-08 15:40:42 -0700444
445 iface->register_method(
446 "AddObject",
447 [&systemConfiguration, &objServer,
James Feistd58879a2019-09-11 11:26:07 -0700448 jsonPointerPath{std::string(jsonPointerPath)}, path{std::string(path)},
449 board](const boost::container::flat_map<std::string, JsonVariantType>&
450 data) {
Patrick Williamsdf190612023-05-10 07:51:34 -0500451 nlohmann::json::json_pointer ptr(jsonPointerPath);
452 nlohmann::json& base = systemConfiguration[ptr];
453 auto findExposes = base.find("Exposes");
James Feist68500ff2018-08-08 15:40:42 -0700454
Patrick Williamsdf190612023-05-10 07:51:34 -0500455 if (findExposes == base.end())
456 {
457 throw std::invalid_argument("Entity must have children.");
458 }
459
460 // this will throw invalid-argument to sdbusplus if invalid json
461 nlohmann::json newData{};
462 for (const auto& item : data)
463 {
464 nlohmann::json& newJson = newData[item.first];
465 std::visit(
466 [&newJson](auto&& val) {
467 newJson = std::forward<decltype(val)>(val);
468 },
469 item.second);
470 }
471
472 auto findName = newData.find("Name");
473 auto findType = newData.find("Type");
474 if (findName == newData.end() || findType == newData.end())
475 {
476 throw std::invalid_argument("AddObject missing Name or Type");
477 }
478 const std::string* type = findType->get_ptr<const std::string*>();
479 const std::string* name = findName->get_ptr<const std::string*>();
480 if (type == nullptr || name == nullptr)
481 {
482 throw std::invalid_argument("Type and Name must be a string.");
483 }
484
485 bool foundNull = false;
486 size_t lastIndex = 0;
487 // we add in the "exposes"
488 for (const auto& expose : *findExposes)
489 {
490 if (expose.is_null())
James Feist68500ff2018-08-08 15:40:42 -0700491 {
Patrick Williamsdf190612023-05-10 07:51:34 -0500492 foundNull = true;
493 continue;
James Feist68500ff2018-08-08 15:40:42 -0700494 }
495
Patrick Williamsdf190612023-05-10 07:51:34 -0500496 if (expose["Name"] == *name && expose["Type"] == *type)
James Feist68500ff2018-08-08 15:40:42 -0700497 {
498 throw std::invalid_argument(
Patrick Williamsdf190612023-05-10 07:51:34 -0500499 "Field already in JSON, not adding");
James Feist68500ff2018-08-08 15:40:42 -0700500 }
Patrick Williamsdf190612023-05-10 07:51:34 -0500501
James Feist02d2b932020-02-06 16:28:48 -0800502 if (foundNull)
503 {
Patrick Williamsdf190612023-05-10 07:51:34 -0500504 continue;
James Feist02d2b932020-02-06 16:28:48 -0800505 }
James Feist68500ff2018-08-08 15:40:42 -0700506
Patrick Williamsdf190612023-05-10 07:51:34 -0500507 lastIndex++;
508 }
James Feistd58879a2019-09-11 11:26:07 -0700509
Patrick Williamsdf190612023-05-10 07:51:34 -0500510 std::ifstream schemaFile(std::string(schemaDirectory) + "/" +
511 boost::to_lower_copy(*type) + ".json");
512 // todo(james) we might want to also make a list of 'can add'
513 // interfaces but for now I think the assumption if there is a
514 // schema avaliable that it is allowed to update is fine
515 if (!schemaFile.good())
516 {
517 throw std::invalid_argument(
518 "No schema avaliable, cannot validate.");
519 }
520 nlohmann::json schema = nlohmann::json::parse(schemaFile, nullptr,
521 false);
522 if (schema.is_discarded())
523 {
524 std::cerr << "Schema not legal" << *type << ".json\n";
525 throw DBusInternalError();
526 }
527 if (!validateJson(schema, newData))
528 {
529 throw std::invalid_argument("Data does not match schema");
530 }
531 if (foundNull)
532 {
533 findExposes->at(lastIndex) = newData;
534 }
535 else
536 {
537 findExposes->push_back(newData);
538 }
539 if (!writeJsonFiles(systemConfiguration))
540 {
541 std::cerr << "Error writing json files\n";
542 throw DBusInternalError();
543 }
544 std::string dbusName = *name;
545
546 std::regex_replace(dbusName.begin(), dbusName.begin(), dbusName.end(),
547 illegalDbusMemberRegex, "_");
548
549 std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
550 createInterface(objServer, path + "/" + dbusName,
551 "xyz.openbmc_project.Configuration." + *type, board,
552 true);
553 // permission is read-write, as since we just created it, must be
554 // runtime modifiable
555 populateInterfaceFromJson(
556 systemConfiguration,
557 jsonPointerPath + "/Exposes/" + std::to_string(lastIndex),
558 interface, newData, objServer,
559 sdbusplus::asio::PropertyPermission::readWrite);
James Feist68500ff2018-08-08 15:40:42 -0700560 });
561 iface->initialize();
562}
563
James Feista465ccc2019-02-08 12:51:01 -0800564void postToDbus(const nlohmann::json& newConfiguration,
565 nlohmann::json& systemConfiguration,
566 sdbusplus::asio::object_server& objServer)
James Feist75fdeeb2018-02-20 14:26:16 -0800567
James Feist1b2e2242018-01-30 13:45:19 -0800568{
Benjamin Fairca2eb042022-09-13 06:40:42 +0000569 Topology topology;
570
James Feist97a63f12018-05-17 13:50:57 -0700571 // iterate through boards
Patrick Williams2594d362022-09-28 06:46:24 -0500572 for (const auto& [boardId, boardConfig] : newConfiguration.items())
James Feist1b2e2242018-01-30 13:45:19 -0800573 {
Andrew Jeffery13132df2022-03-25 13:29:41 +1030574 std::string boardKey = boardConfig["Name"];
575 std::string boardKeyOrig = boardConfig["Name"];
576 std::string jsonPointerPath = "/" + boardId;
James Feist97a63f12018-05-17 13:50:57 -0700577 // loop through newConfiguration, but use values from system
578 // configuration to be able to modify via dbus later
Andrew Jeffery13132df2022-03-25 13:29:41 +1030579 auto boardValues = systemConfiguration[boardId];
James Feistd63d18a2018-07-19 15:23:45 -0700580 auto findBoardType = boardValues.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -0800581 std::string boardType;
582 if (findBoardType != boardValues.end() &&
583 findBoardType->type() == nlohmann::json::value_t::string)
584 {
585 boardType = findBoardType->get<std::string>();
586 std::regex_replace(boardType.begin(), boardType.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800587 boardType.end(), illegalDbusMemberRegex, "_");
James Feist1b2e2242018-01-30 13:45:19 -0800588 }
589 else
590 {
591 std::cerr << "Unable to find type for " << boardKey
592 << " reverting to Chassis.\n";
593 boardType = "Chassis";
594 }
James Feist11be6672018-04-06 14:05:32 -0700595 std::string boardtypeLower = boost::algorithm::to_lower_copy(boardType);
James Feist1b2e2242018-01-30 13:45:19 -0800596
597 std::regex_replace(boardKey.begin(), boardKey.begin(), boardKey.end(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800598 illegalDbusMemberRegex, "_");
599 std::string boardName = "/xyz/openbmc_project/inventory/system/";
600 boardName += boardtypeLower;
601 boardName += "/";
602 boardName += boardKey;
James Feist1b2e2242018-01-30 13:45:19 -0800603
James Feistd58879a2019-09-11 11:26:07 -0700604 std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
605 createInterface(objServer, boardName,
606 "xyz.openbmc_project.Inventory.Item", boardKey);
James Feist68500ff2018-08-08 15:40:42 -0700607
James Feistd58879a2019-09-11 11:26:07 -0700608 std::shared_ptr<sdbusplus::asio::dbus_interface> boardIface =
609 createInterface(objServer, boardName,
610 "xyz.openbmc_project.Inventory.Item." + boardType,
611 boardKeyOrig);
James Feist11be6672018-04-06 14:05:32 -0700612
James Feist68500ff2018-08-08 15:40:42 -0700613 createAddObjectMethod(jsonPointerPath, boardName, systemConfiguration,
James Feistd58879a2019-09-11 11:26:07 -0700614 objServer, boardKeyOrig);
James Feist68500ff2018-08-08 15:40:42 -0700615
James Feist97a63f12018-05-17 13:50:57 -0700616 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
James Feistc6248a52018-08-14 10:09:45 -0700617 boardIface, boardValues, objServer);
James Feist97a63f12018-05-17 13:50:57 -0700618 jsonPointerPath += "/";
619 // iterate through board properties
Patrick Williams2594d362022-09-28 06:46:24 -0500620 for (const auto& [propName, propValue] : boardValues.items())
James Feist11be6672018-04-06 14:05:32 -0700621 {
Andrew Jefferya96950d2022-03-25 13:32:46 +1030622 if (propValue.type() == nlohmann::json::value_t::object)
James Feist11be6672018-04-06 14:05:32 -0700623 {
James Feistd58879a2019-09-11 11:26:07 -0700624 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
Andrew Jefferya96950d2022-03-25 13:32:46 +1030625 createInterface(objServer, boardName, propName,
James Feistd58879a2019-09-11 11:26:07 -0700626 boardKeyOrig);
627
James Feistc6248a52018-08-14 10:09:45 -0700628 populateInterfaceFromJson(systemConfiguration,
Andrew Jefferya96950d2022-03-25 13:32:46 +1030629 jsonPointerPath + propName, iface,
630 propValue, objServer);
James Feist11be6672018-04-06 14:05:32 -0700631 }
632 }
James Feist97a63f12018-05-17 13:50:57 -0700633
James Feist1e3e6982018-08-03 16:09:28 -0700634 auto exposes = boardValues.find("Exposes");
James Feist1b2e2242018-01-30 13:45:19 -0800635 if (exposes == boardValues.end())
636 {
637 continue;
638 }
James Feist97a63f12018-05-17 13:50:57 -0700639 // iterate through exposes
James Feist1e3e6982018-08-03 16:09:28 -0700640 jsonPointerPath += "Exposes/";
James Feist97a63f12018-05-17 13:50:57 -0700641
642 // store the board level pointer so we can modify it on the way down
643 std::string jsonPointerPathBoard = jsonPointerPath;
644 size_t exposesIndex = -1;
James Feista465ccc2019-02-08 12:51:01 -0800645 for (auto& item : *exposes)
James Feist1b2e2242018-01-30 13:45:19 -0800646 {
James Feist97a63f12018-05-17 13:50:57 -0700647 exposesIndex++;
648 jsonPointerPath = jsonPointerPathBoard;
649 jsonPointerPath += std::to_string(exposesIndex);
650
James Feistd63d18a2018-07-19 15:23:45 -0700651 auto findName = item.find("Name");
James Feist1b2e2242018-01-30 13:45:19 -0800652 if (findName == item.end())
653 {
654 std::cerr << "cannot find name in field " << item << "\n";
655 continue;
656 }
James Feist1e3e6982018-08-03 16:09:28 -0700657 auto findStatus = item.find("Status");
James Feist1b2e2242018-01-30 13:45:19 -0800658 // if status is not found it is assumed to be status = 'okay'
659 if (findStatus != item.end())
660 {
661 if (*findStatus == "disabled")
662 {
663 continue;
664 }
665 }
James Feistd63d18a2018-07-19 15:23:45 -0700666 auto findType = item.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -0800667 std::string itemType;
668 if (findType != item.end())
669 {
670 itemType = findType->get<std::string>();
671 std::regex_replace(itemType.begin(), itemType.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800672 itemType.end(), illegalDbusPathRegex, "_");
James Feist1b2e2242018-01-30 13:45:19 -0800673 }
674 else
675 {
676 itemType = "unknown";
677 }
678 std::string itemName = findName->get<std::string>();
679 std::regex_replace(itemName.begin(), itemName.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800680 itemName.end(), illegalDbusMemberRegex, "_");
681 std::string ifacePath = boardName;
682 ifacePath += "/";
683 ifacePath += itemName;
James Feistc6248a52018-08-14 10:09:45 -0700684
James Feistd58879a2019-09-11 11:26:07 -0700685 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface =
Ed Tanous07d467b2021-02-23 14:48:37 -0800686 createInterface(objServer, ifacePath,
James Feistd58879a2019-09-11 11:26:07 -0700687 "xyz.openbmc_project.Configuration." + itemType,
688 boardKeyOrig);
James Feist1b2e2242018-01-30 13:45:19 -0800689
Sui Chen74ebe592022-09-13 10:22:03 -0700690 if (itemType == "BMC")
691 {
692 std::shared_ptr<sdbusplus::asio::dbus_interface> bmcIface =
693 createInterface(objServer, ifacePath,
694 "xyz.openbmc_project.Inventory.Item.Bmc",
695 boardKeyOrig);
696 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
697 bmcIface, item, objServer,
698 getPermission(itemType));
699 }
Edward Leeeb587b42023-03-08 18:59:04 +0000700 else if (itemType == "System")
701 {
702 std::shared_ptr<sdbusplus::asio::dbus_interface> systemIface =
703 createInterface(objServer, ifacePath,
704 "xyz.openbmc_project.Inventory.Item.System",
705 boardKeyOrig);
706 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
707 systemIface, item, objServer,
708 getPermission(itemType));
709 }
Sui Chen74ebe592022-09-13 10:22:03 -0700710
James Feist97a63f12018-05-17 13:50:57 -0700711 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
James Feistc6248a52018-08-14 10:09:45 -0700712 itemIface, item, objServer,
713 getPermission(itemType));
James Feist1b2e2242018-01-30 13:45:19 -0800714
Patrick Williams2594d362022-09-28 06:46:24 -0500715 for (const auto& [name, config] : item.items())
James Feist1b2e2242018-01-30 13:45:19 -0800716 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030717 jsonPointerPath = jsonPointerPathBoard;
718 jsonPointerPath.append(std::to_string(exposesIndex))
719 .append("/")
720 .append(name);
721 if (config.type() == nlohmann::json::value_t::object)
James Feist1b2e2242018-01-30 13:45:19 -0800722 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030723 std::string ifaceName =
724 "xyz.openbmc_project.Configuration.";
725 ifaceName.append(itemType).append(".").append(name);
James Feist97a63f12018-05-17 13:50:57 -0700726
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030727 std::shared_ptr<sdbusplus::asio::dbus_interface>
728 objectIface = createInterface(objServer, ifacePath,
729 ifaceName, boardKeyOrig);
730
731 populateInterfaceFromJson(
732 systemConfiguration, jsonPointerPath, objectIface,
733 config, objServer, getPermission(name));
James Feist1b2e2242018-01-30 13:45:19 -0800734 }
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030735 else if (config.type() == nlohmann::json::value_t::array)
James Feist1b2e2242018-01-30 13:45:19 -0800736 {
737 size_t index = 0;
Ed Tanous3013fb42022-07-09 08:27:06 -0700738 if (config.empty())
James Feist1b2e2242018-01-30 13:45:19 -0800739 {
James Feist8f2710a2018-05-09 17:18:55 -0700740 continue;
741 }
742 bool isLegal = true;
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030743 auto type = config[0].type();
James Feist8f2710a2018-05-09 17:18:55 -0700744 if (type != nlohmann::json::value_t::object)
745 {
746 continue;
747 }
748
749 // verify legal json
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030750 for (const auto& arrayItem : config)
James Feist8f2710a2018-05-09 17:18:55 -0700751 {
752 if (arrayItem.type() != type)
James Feist1b2e2242018-01-30 13:45:19 -0800753 {
James Feist8f2710a2018-05-09 17:18:55 -0700754 isLegal = false;
James Feist1b2e2242018-01-30 13:45:19 -0800755 break;
756 }
James Feist8f2710a2018-05-09 17:18:55 -0700757 }
758 if (!isLegal)
759 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030760 std::cerr << "dbus format error" << config << "\n";
James Feist8f2710a2018-05-09 17:18:55 -0700761 break;
762 }
763
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030764 for (auto& arrayItem : config)
James Feist8f2710a2018-05-09 17:18:55 -0700765 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030766 std::string ifaceName =
767 "xyz.openbmc_project.Configuration.";
768 ifaceName.append(itemType).append(".").append(name);
769 ifaceName.append(std::to_string(index));
James Feist97a63f12018-05-17 13:50:57 -0700770
James Feistd58879a2019-09-11 11:26:07 -0700771 std::shared_ptr<sdbusplus::asio::dbus_interface>
772 objectIface = createInterface(
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030773 objServer, ifacePath, ifaceName, boardKeyOrig);
James Feistd58879a2019-09-11 11:26:07 -0700774
James Feistc6248a52018-08-14 10:09:45 -0700775 populateInterfaceFromJson(
776 systemConfiguration,
777 jsonPointerPath + "/" + std::to_string(index),
778 objectIface, arrayItem, objServer,
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030779 getPermission(name));
James Feistbb43d022018-06-12 15:44:33 -0700780 index++;
James Feist1b2e2242018-01-30 13:45:19 -0800781 }
782 }
783 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000784
785 topology.addBoard(boardName, boardType, item);
James Feist1b2e2242018-01-30 13:45:19 -0800786 }
787 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000788
789 for (const auto& boardAssoc : topology.getAssocs())
790 {
791 auto ifacePtr = objServer.add_interface(
792 boardAssoc.first, "xyz.openbmc_project.Association.Definitions");
793
794 ifacePtr->register_property("Associations", boardAssoc.second);
795 ifacePtr->initialize();
796 }
James Feist1b2e2242018-01-30 13:45:19 -0800797}
798
James Feist8f2710a2018-05-09 17:18:55 -0700799// reads json files out of the filesystem
Andrew Jefferyf3311792022-03-29 22:09:00 +1030800bool loadConfigurations(std::list<nlohmann::json>& configurations)
James Feist3cb5fec2018-01-23 14:41:51 -0800801{
802 // find configuration files
Ed Tanous072e25d2018-12-16 21:45:20 -0800803 std::vector<std::filesystem::path> jsonPaths;
Andrew Jefferya9c58922021-06-01 09:28:59 +0930804 if (!findFiles(
805 std::vector<std::filesystem::path>{configurationDirectory,
806 hostConfigurationDirectory},
807 R"(.*\.json)", jsonPaths))
James Feist3cb5fec2018-01-23 14:41:51 -0800808 {
809 std::cerr << "Unable to find any configuration files in "
James Feistb4383f42018-08-06 16:54:10 -0700810 << configurationDirectory << "\n";
James Feist75fdeeb2018-02-20 14:26:16 -0800811 return false;
James Feist3cb5fec2018-01-23 14:41:51 -0800812 }
James Feistb4383f42018-08-06 16:54:10 -0700813
814 std::ifstream schemaStream(std::string(schemaDirectory) + "/" +
815 globalSchema);
816 if (!schemaStream.good())
817 {
818 std::cerr
819 << "Cannot open schema file, cannot validate JSON, exiting\n\n";
820 std::exit(EXIT_FAILURE);
Ed Tanous072e25d2018-12-16 21:45:20 -0800821 return false;
James Feistb4383f42018-08-06 16:54:10 -0700822 }
823 nlohmann::json schema = nlohmann::json::parse(schemaStream, nullptr, false);
824 if (schema.is_discarded())
825 {
826 std::cerr
827 << "Illegal schema file detected, cannot validate JSON, exiting\n";
828 std::exit(EXIT_FAILURE);
Ed Tanous072e25d2018-12-16 21:45:20 -0800829 return false;
James Feistb4383f42018-08-06 16:54:10 -0700830 }
831
James Feista465ccc2019-02-08 12:51:01 -0800832 for (auto& jsonPath : jsonPaths)
James Feist3cb5fec2018-01-23 14:41:51 -0800833 {
834 std::ifstream jsonStream(jsonPath.c_str());
835 if (!jsonStream.good())
836 {
837 std::cerr << "unable to open " << jsonPath.string() << "\n";
838 continue;
839 }
840 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
841 if (data.is_discarded())
842 {
843 std::cerr << "syntax error in " << jsonPath.string() << "\n";
844 continue;
845 }
James Feist8da99192019-01-24 08:20:16 -0800846 /*
847 * todo(james): reenable this once less things are in flight
848 *
James Feistb4383f42018-08-06 16:54:10 -0700849 if (!validateJson(schema, data))
850 {
851 std::cerr << "Error validating " << jsonPath.string() << "\n";
852 continue;
853 }
James Feist8da99192019-01-24 08:20:16 -0800854 */
James Feistb4383f42018-08-06 16:54:10 -0700855
James Feist3cb5fec2018-01-23 14:41:51 -0800856 if (data.type() == nlohmann::json::value_t::array)
857 {
James Feista465ccc2019-02-08 12:51:01 -0800858 for (auto& d : data)
James Feist3cb5fec2018-01-23 14:41:51 -0800859 {
860 configurations.emplace_back(d);
861 }
862 }
863 else
864 {
865 configurations.emplace_back(data);
866 }
867 }
Ed Tanous072e25d2018-12-16 21:45:20 -0800868 return true;
James Feist75fdeeb2018-02-20 14:26:16 -0800869}
James Feist3cb5fec2018-01-23 14:41:51 -0800870
Andrew Jeffery55192932022-03-24 12:29:27 +1030871static bool deviceRequiresPowerOn(const nlohmann::json& entity)
872{
873 auto powerState = entity.find("PowerState");
Andrew Jefferyb6209442022-03-24 12:36:20 +1030874 if (powerState == entity.end())
Andrew Jeffery55192932022-03-24 12:29:27 +1030875 {
Andrew Jefferyb6209442022-03-24 12:36:20 +1030876 return false;
Andrew Jeffery55192932022-03-24 12:29:27 +1030877 }
878
Ed Tanous3013fb42022-07-09 08:27:06 -0700879 const auto* ptr = powerState->get_ptr<const std::string*>();
880 if (ptr == nullptr)
Andrew Jefferyb6209442022-03-24 12:36:20 +1030881 {
882 return false;
883 }
884
885 return *ptr == "On" || *ptr == "BiosPost";
Andrew Jeffery55192932022-03-24 12:29:27 +1030886}
887
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030888static void pruneDevice(const nlohmann::json& systemConfiguration,
889 const bool powerOff, const bool scannedPowerOff,
890 const std::string& name, const nlohmann::json& device)
891{
892 if (systemConfiguration.contains(name))
893 {
894 return;
895 }
896
Andrew Jeffery4db38bc2022-03-24 13:42:41 +1030897 if (deviceRequiresPowerOn(device) && (powerOff || scannedPowerOff))
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030898 {
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030899 return;
900 }
901
902 logDeviceRemoved(device);
903}
904
James Feistb1728ca2020-04-30 15:40:55 -0700905void startRemovedTimer(boost::asio::steady_timer& timer,
James Feist1df06a42019-04-11 14:23:04 -0700906 nlohmann::json& systemConfiguration)
907{
908 static bool scannedPowerOff = false;
909 static bool scannedPowerOn = false;
910
James Feistfb00f392019-06-25 14:16:48 -0700911 if (systemConfiguration.empty() || lastJson.empty())
912 {
913 return; // not ready yet
914 }
James Feist1df06a42019-04-11 14:23:04 -0700915 if (scannedPowerOn)
916 {
917 return;
918 }
919
920 if (!isPowerOn() && scannedPowerOff)
921 {
922 return;
923 }
924
James Feistb1728ca2020-04-30 15:40:55 -0700925 timer.expires_after(std::chrono::seconds(10));
Andrew Jeffery27a1cfb2022-03-24 12:31:53 +1030926 timer.async_wait(
927 [&systemConfiguration](const boost::system::error_code& ec) {
Patrick Williamsdf190612023-05-10 07:51:34 -0500928 if (ec == boost::asio::error::operation_aborted)
929 {
930 return;
931 }
Andrew Jeffery27a1cfb2022-03-24 12:31:53 +1030932
Patrick Williamsdf190612023-05-10 07:51:34 -0500933 bool powerOff = !isPowerOn();
934 for (const auto& [name, device] : lastJson.items())
935 {
936 pruneDevice(systemConfiguration, powerOff, scannedPowerOff, name,
937 device);
938 }
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030939
Patrick Williamsdf190612023-05-10 07:51:34 -0500940 scannedPowerOff = true;
941 if (!powerOff)
942 {
943 scannedPowerOn = true;
944 }
945 });
James Feist1df06a42019-04-11 14:23:04 -0700946}
947
Andrew Jeffery2f750d22022-03-24 14:32:57 +1030948static std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>&
949 getDeviceInterfaces(const nlohmann::json& device)
950{
951 return inventory[device["Name"].get<std::string>()];
952}
953
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030954static void pruneConfiguration(nlohmann::json& systemConfiguration,
955 sdbusplus::asio::object_server& objServer,
956 bool powerOff, const std::string& name,
957 const nlohmann::json& device)
958{
959 if (powerOff && deviceRequiresPowerOn(device))
960 {
961 // power not on yet, don't know if it's there or not
962 return;
963 }
964
965 auto& ifaces = getDeviceInterfaces(device);
966 for (auto& iface : ifaces)
967 {
968 auto sharedPtr = iface.lock();
969 if (!!sharedPtr)
970 {
971 objServer.remove_interface(sharedPtr);
972 }
973 }
974
975 ifaces.clear();
976 systemConfiguration.erase(name);
977 logDeviceRemoved(device);
978}
979
Andrew Jeffery7c4cbbe2022-03-24 15:27:10 +1030980static void deriveNewConfiguration(const nlohmann::json& oldConfiguration,
981 nlohmann::json& newConfiguration)
982{
983 for (auto it = newConfiguration.begin(); it != newConfiguration.end();)
984 {
985 auto findKey = oldConfiguration.find(it.key());
986 if (findKey != oldConfiguration.end())
987 {
988 it = newConfiguration.erase(it);
989 }
990 else
991 {
992 it++;
993 }
994 }
995}
996
Andrew Jefferye35d0ac2022-03-24 15:53:13 +1030997static void publishNewConfiguration(
998 const size_t& instance, const size_t count,
999 boost::asio::steady_timer& timer, nlohmann::json& systemConfiguration,
1000 // Gerrit discussion:
1001 // https://gerrit.openbmc-project.xyz/c/openbmc/entity-manager/+/52316/6
1002 //
1003 // Discord discussion:
1004 // https://discord.com/channels/775381525260664832/867820390406422538/958048437729910854
1005 //
1006 // NOLINTNEXTLINE(performance-unnecessary-value-param)
1007 const nlohmann::json newConfiguration,
1008 sdbusplus::asio::object_server& objServer)
1009{
1010 loadOverlays(newConfiguration);
1011
Ed Tanous49a888c2023-03-06 13:44:51 -08001012 boost::asio::post(io, [systemConfiguration]() {
Andrew Jefferye35d0ac2022-03-24 15:53:13 +10301013 if (!writeJsonFiles(systemConfiguration))
1014 {
1015 std::cerr << "Error writing json files\n";
1016 }
1017 });
1018
Ed Tanous49a888c2023-03-06 13:44:51 -08001019 boost::asio::post(io, [&instance, count, &timer, newConfiguration,
1020 &systemConfiguration, &objServer]() {
Andrew Jefferye35d0ac2022-03-24 15:53:13 +10301021 postToDbus(newConfiguration, systemConfiguration, objServer);
1022 if (count == instance)
1023 {
1024 startRemovedTimer(timer, systemConfiguration);
1025 }
1026 });
1027}
1028
James Feist8f2710a2018-05-09 17:18:55 -07001029// main properties changed entry
James Feist4dc617b2020-05-01 09:54:47 -07001030void propertiesChangedCallback(nlohmann::json& systemConfiguration,
1031 sdbusplus::asio::object_server& objServer)
James Feist8f2710a2018-05-09 17:18:55 -07001032{
James Feist2539ccd2020-05-01 16:15:08 -07001033 static bool inProgress = false;
James Feistb1728ca2020-04-30 15:40:55 -07001034 static boost::asio::steady_timer timer(io);
James Feist899e17f2019-09-13 11:46:29 -07001035 static size_t instance = 0;
1036 instance++;
1037 size_t count = instance;
James Feist1df06a42019-04-11 14:23:04 -07001038
James Feistb1728ca2020-04-30 15:40:55 -07001039 timer.expires_after(std::chrono::seconds(5));
James Feist8f2710a2018-05-09 17:18:55 -07001040
1041 // setup an async wait as we normally get flooded with new requests
James Feist4dc617b2020-05-01 09:54:47 -07001042 timer.async_wait([&systemConfiguration, &objServer,
James Feist899e17f2019-09-13 11:46:29 -07001043 count](const boost::system::error_code& ec) {
James Feist8f2710a2018-05-09 17:18:55 -07001044 if (ec == boost::asio::error::operation_aborted)
1045 {
1046 // we were cancelled
1047 return;
1048 }
Ed Tanous07d467b2021-02-23 14:48:37 -08001049 if (ec)
James Feist8f2710a2018-05-09 17:18:55 -07001050 {
1051 std::cerr << "async wait error " << ec << "\n";
1052 return;
1053 }
1054
James Feist2539ccd2020-05-01 16:15:08 -07001055 if (inProgress)
1056 {
1057 propertiesChangedCallback(systemConfiguration, objServer);
1058 return;
1059 }
1060 inProgress = true;
1061
James Feist8f2710a2018-05-09 17:18:55 -07001062 nlohmann::json oldConfiguration = systemConfiguration;
James Feist899e17f2019-09-13 11:46:29 -07001063 auto missingConfigurations = std::make_shared<nlohmann::json>();
1064 *missingConfigurations = systemConfiguration;
1065
James Feist8f2710a2018-05-09 17:18:55 -07001066 std::list<nlohmann::json> configurations;
Andrew Jefferyf3311792022-03-29 22:09:00 +10301067 if (!loadConfigurations(configurations))
James Feist8f2710a2018-05-09 17:18:55 -07001068 {
Andrew Jefferyf3311792022-03-29 22:09:00 +10301069 std::cerr << "Could not load configurations\n";
James Feist2539ccd2020-05-01 16:15:08 -07001070 inProgress = false;
James Feist8f2710a2018-05-09 17:18:55 -07001071 return;
1072 }
1073
1074 auto perfScan = std::make_shared<PerformScan>(
James Feist899e17f2019-09-13 11:46:29 -07001075 systemConfiguration, *missingConfigurations, configurations,
James Feist4dc617b2020-05-01 09:54:47 -07001076 objServer,
1077 [&systemConfiguration, &objServer, count, oldConfiguration,
1078 missingConfigurations]() {
Patrick Williamsdf190612023-05-10 07:51:34 -05001079 // this is something that since ac has been applied to the bmc
1080 // we saw, and we no longer see it
1081 bool powerOff = !isPowerOn();
1082 for (const auto& [name, device] : missingConfigurations->items())
1083 {
1084 pruneConfiguration(systemConfiguration, objServer, powerOff,
1085 name, device);
1086 }
James Feist899e17f2019-09-13 11:46:29 -07001087
Patrick Williamsdf190612023-05-10 07:51:34 -05001088 nlohmann::json newConfiguration = systemConfiguration;
Andrew Jeffery7c4cbbe2022-03-24 15:27:10 +10301089
Patrick Williamsdf190612023-05-10 07:51:34 -05001090 deriveNewConfiguration(oldConfiguration, newConfiguration);
Andrew Jeffery7c4cbbe2022-03-24 15:27:10 +10301091
Patrick Williamsdf190612023-05-10 07:51:34 -05001092 for (const auto& [_, device] : newConfiguration.items())
1093 {
1094 logDeviceAdded(device);
1095 }
James Feist899e17f2019-09-13 11:46:29 -07001096
Patrick Williamsdf190612023-05-10 07:51:34 -05001097 inProgress = false;
James Feist2539ccd2020-05-01 16:15:08 -07001098
Patrick Williamsdf190612023-05-10 07:51:34 -05001099 boost::asio::post(
1100 io, std::bind_front(publishNewConfiguration, std::ref(instance),
1101 count, std::ref(timer),
1102 std::ref(systemConfiguration),
1103 newConfiguration, std::ref(objServer)));
James Feist8f2710a2018-05-09 17:18:55 -07001104 });
1105 perfScan->run();
1106 });
James Feist75fdeeb2018-02-20 14:26:16 -08001107}
1108
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001109// Extract the D-Bus interfaces to probe from the JSON config files.
1110static std::set<std::string> getProbeInterfaces()
1111{
1112 std::set<std::string> interfaces;
1113 std::list<nlohmann::json> configurations;
1114 if (!loadConfigurations(configurations))
1115 {
1116 return interfaces;
1117 }
1118
1119 for (auto it = configurations.begin(); it != configurations.end();)
1120 {
1121 auto findProbe = it->find("Probe");
1122 if (findProbe == it->end())
1123 {
1124 std::cerr << "configuration file missing probe:\n " << *it << "\n";
1125 it++;
1126 continue;
1127 }
1128
1129 nlohmann::json probeCommand;
1130 if ((*findProbe).type() != nlohmann::json::value_t::array)
1131 {
1132 probeCommand = nlohmann::json::array();
1133 probeCommand.push_back(*findProbe);
1134 }
1135 else
1136 {
1137 probeCommand = *findProbe;
1138 }
1139
1140 for (const nlohmann::json& probeJson : probeCommand)
1141 {
1142 const std::string* probe = probeJson.get_ptr<const std::string*>();
1143 if (probe == nullptr)
1144 {
1145 std::cerr << "Probe statement wasn't a string, can't parse";
1146 continue;
1147 }
1148 // Skip it if the probe cmd doesn't contain an interface.
1149 if (findProbeType(*probe))
1150 {
1151 continue;
1152 }
1153
1154 // syntax requires probe before first open brace
1155 auto findStart = probe->find('(');
1156 if (findStart != std::string::npos)
1157 {
1158 std::string interface = probe->substr(0, findStart);
1159 interfaces.emplace(interface);
1160 }
1161 }
1162 it++;
1163 }
1164
1165 return interfaces;
1166}
1167
1168// Check if InterfacesAdded payload contains an iface that needs probing.
1169static bool
1170 iaContainsProbeInterface(sdbusplus::message_t& msg,
1171 const std::set<std::string>& probeInterfaces)
1172{
1173 sdbusplus::message::object_path path;
1174 DBusObject interfaces;
1175 std::set<std::string> interfaceSet;
1176 std::set<std::string> intersect;
1177
1178 msg.read(path, interfaces);
1179
1180 std::for_each(interfaces.begin(), interfaces.end(),
1181 [&interfaceSet](const auto& iface) {
Patrick Williamsdf190612023-05-10 07:51:34 -05001182 interfaceSet.insert(iface.first);
1183 });
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001184
1185 std::set_intersection(interfaceSet.begin(), interfaceSet.end(),
1186 probeInterfaces.begin(), probeInterfaces.end(),
1187 std::inserter(intersect, intersect.end()));
1188 return !intersect.empty();
1189}
1190
1191// Check if InterfacesRemoved payload contains an iface that needs probing.
1192static bool
1193 irContainsProbeInterface(sdbusplus::message_t& msg,
1194 const std::set<std::string>& probeInterfaces)
1195{
1196 sdbusplus::message::object_path path;
1197 std::set<std::string> interfaces;
1198 std::set<std::string> intersect;
1199
1200 msg.read(path, interfaces);
1201
1202 std::set_intersection(interfaces.begin(), interfaces.end(),
1203 probeInterfaces.begin(), probeInterfaces.end(),
1204 std::inserter(intersect, intersect.end()));
1205 return !intersect.empty();
1206}
1207
James Feist98132792019-07-09 13:29:09 -07001208int main()
James Feist75fdeeb2018-02-20 14:26:16 -08001209{
1210 // setup connection to dbus
Ed Tanous07d467b2021-02-23 14:48:37 -08001211 systemBus = std::make_shared<sdbusplus::asio::connection>(io);
1212 systemBus->request_name("xyz.openbmc_project.EntityManager");
James Feist4131aea2018-03-09 09:47:30 -08001213
Nan Zhoua3315672022-09-20 19:48:14 +00001214 // The EntityManager object itself doesn't expose any properties.
1215 // No need to set up ObjectManager for the |EntityManager| object.
1216 sdbusplus::asio::object_server objServer(systemBus, /*skipManager=*/true);
1217
1218 // All other objects that EntityManager currently support are under the
1219 // inventory subtree.
1220 // See the discussion at
1221 // https://discord.com/channels/775381525260664832/1018929092009144380
1222 objServer.add_manager("/xyz/openbmc_project/inventory");
James Feistfd1264a2018-05-03 12:10:00 -07001223
James Feist8f2710a2018-05-09 17:18:55 -07001224 std::shared_ptr<sdbusplus::asio::dbus_interface> entityIface =
1225 objServer.add_interface("/xyz/openbmc_project/EntityManager",
1226 "xyz.openbmc_project.EntityManager");
James Feistfd1264a2018-05-03 12:10:00 -07001227
James Feist4131aea2018-03-09 09:47:30 -08001228 // to keep reference to the match / filter objects so they don't get
1229 // destroyed
James Feist8f2710a2018-05-09 17:18:55 -07001230
1231 nlohmann::json systemConfiguration = nlohmann::json::object();
1232
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001233 std::set<std::string> probeInterfaces = getProbeInterfaces();
1234
Brad Bishopc76af0f2020-12-04 13:50:23 -05001235 // We need a poke from DBus for static providers that create all their
1236 // objects prior to claiming a well-known name, and thus don't emit any
1237 // org.freedesktop.DBus.Properties signals. Similarly if a process exits
1238 // for any reason, expected or otherwise, we'll need a poke to remove
1239 // entities from DBus.
Patrick Williams2af39222022-07-22 19:26:56 -05001240 sdbusplus::bus::match_t nameOwnerChangedMatch(
1241 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishopc76af0f2020-12-04 13:50:23 -05001242 sdbusplus::bus::match::rules::nameOwnerChanged(),
Patrick Williams7b8786f2022-10-10 10:23:37 -05001243 [&](sdbusplus::message_t& m) {
Patrick Williamsdf190612023-05-10 07:51:34 -05001244 auto [name, oldOwner,
1245 newOwner] = m.unpack<std::string, std::string, std::string>();
Patrick Williams7b8786f2022-10-10 10:23:37 -05001246
Patrick Williamsdf190612023-05-10 07:51:34 -05001247 if (name.starts_with(':'))
1248 {
1249 // We should do nothing with unique-name connections.
1250 return;
1251 }
Patrick Williams7b8786f2022-10-10 10:23:37 -05001252
Patrick Williamsdf190612023-05-10 07:51:34 -05001253 propertiesChangedCallback(systemConfiguration, objServer);
Brad Bishopc76af0f2020-12-04 13:50:23 -05001254 });
Brad Bishop10a8c5f2020-12-07 21:40:07 -05001255 // We also need a poke from DBus when new interfaces are created or
1256 // destroyed.
Patrick Williams2af39222022-07-22 19:26:56 -05001257 sdbusplus::bus::match_t interfacesAddedMatch(
1258 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishop10a8c5f2020-12-07 21:40:07 -05001259 sdbusplus::bus::match::rules::interfacesAdded(),
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001260 [&](sdbusplus::message_t& msg) {
Patrick Williamsdf190612023-05-10 07:51:34 -05001261 if (iaContainsProbeInterface(msg, probeInterfaces))
1262 {
1263 propertiesChangedCallback(systemConfiguration, objServer);
1264 }
Brad Bishop10a8c5f2020-12-07 21:40:07 -05001265 });
Patrick Williams2af39222022-07-22 19:26:56 -05001266 sdbusplus::bus::match_t interfacesRemovedMatch(
1267 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishop10a8c5f2020-12-07 21:40:07 -05001268 sdbusplus::bus::match::rules::interfacesRemoved(),
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001269 [&](sdbusplus::message_t& msg) {
Patrick Williamsdf190612023-05-10 07:51:34 -05001270 if (irContainsProbeInterface(msg, probeInterfaces))
1271 {
1272 propertiesChangedCallback(systemConfiguration, objServer);
1273 }
Brad Bishop10a8c5f2020-12-07 21:40:07 -05001274 });
Brad Bishopc76af0f2020-12-04 13:50:23 -05001275
Ed Tanous49a888c2023-03-06 13:44:51 -08001276 boost::asio::post(io, [&]() {
1277 propertiesChangedCallback(systemConfiguration, objServer);
1278 });
James Feist4131aea2018-03-09 09:47:30 -08001279
James Feistfd1264a2018-05-03 12:10:00 -07001280 entityIface->register_method("ReScan", [&]() {
James Feist4dc617b2020-05-01 09:54:47 -07001281 propertiesChangedCallback(systemConfiguration, objServer);
James Feist75fdeeb2018-02-20 14:26:16 -08001282 });
James Feist8f2710a2018-05-09 17:18:55 -07001283 entityIface->initialize();
1284
James Feist1df06a42019-04-11 14:23:04 -07001285 if (fwVersionIsSame())
1286 {
1287 if (std::filesystem::is_regular_file(currentConfiguration))
1288 {
1289 // this file could just be deleted, but it's nice for debug
1290 std::filesystem::create_directory(tempConfigDir);
1291 std::filesystem::remove(lastConfiguration);
1292 std::filesystem::copy(currentConfiguration, lastConfiguration);
1293 std::filesystem::remove(currentConfiguration);
1294
1295 std::ifstream jsonStream(lastConfiguration);
1296 if (jsonStream.good())
1297 {
1298 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
1299 if (data.is_discarded())
1300 {
1301 std::cerr << "syntax error in " << lastConfiguration
1302 << "\n";
1303 }
1304 else
1305 {
1306 lastJson = std::move(data);
1307 }
1308 }
1309 else
1310 {
1311 std::cerr << "unable to open " << lastConfiguration << "\n";
1312 }
1313 }
1314 }
1315 else
1316 {
1317 // not an error, just logging at this level to make it in the journal
1318 std::cerr << "Clearing previous configuration\n";
1319 std::filesystem::remove(currentConfiguration);
1320 }
1321
1322 // some boards only show up after power is on, we want to not say they are
1323 // removed until the same state happens
Ed Tanous07d467b2021-02-23 14:48:37 -08001324 setupPowerMatch(systemBus);
James Feist1df06a42019-04-11 14:23:04 -07001325
James Feist1b2e2242018-01-30 13:45:19 -08001326 io.run();
James Feist3cb5fec2018-01-23 14:41:51 -08001327
1328 return 0;
James Feist75fdeeb2018-02-20 14:26:16 -08001329}