blob: f023e64f02a09e9768f0e691e06b05d876584b5a [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
John Edward Broadbentd97c6312023-10-26 20:32:07 +000086void tryIfaceInitialize(std::shared_ptr<sdbusplus::asio::dbus_interface>& iface)
87{
88 try
89 {
90 iface->initialize();
91 }
92 catch (std::exception& e)
93 {
94 std::cerr << "Unable to initialize dbus interface : " << e.what()
95 << "\n"
96 << "object Path : " << iface->get_object_path() << "\n"
97 << "interface name : " << iface->get_interface_name() << "\n";
98 }
99}
100
Andrew Jeffery666583b2021-12-01 15:50:12 +1030101FoundProbeTypeT findProbeType(const std::string& probe)
102{
103 boost::container::flat_map<const char*, probe_type_codes,
104 CmpStr>::const_iterator probeType;
105 for (probeType = probeTypes.begin(); probeType != probeTypes.end();
106 ++probeType)
107 {
108 if (probe.find(probeType->first) != std::string::npos)
109 {
110 return probeType;
111 }
112 }
113
114 return std::nullopt;
115}
116
James Feistd58879a2019-09-11 11:26:07 -0700117static std::shared_ptr<sdbusplus::asio::dbus_interface>
118 createInterface(sdbusplus::asio::object_server& objServer,
119 const std::string& path, const std::string& interface,
James Feist02d2b932020-02-06 16:28:48 -0800120 const std::string& parent, bool checkNull = false)
James Feistd58879a2019-09-11 11:26:07 -0700121{
James Feist02d2b932020-02-06 16:28:48 -0800122 // on first add we have no reason to check for null before add, as there
123 // won't be any. For dynamically added interfaces, we check for null so that
124 // a constant delete/add will not create a memory leak
125
126 auto ptr = objServer.add_interface(path, interface);
127 auto& dataVector = inventory[parent];
128 if (checkNull)
129 {
130 auto it = std::find_if(dataVector.begin(), dataVector.end(),
131 [](const auto& p) { return p.expired(); });
132 if (it != dataVector.end())
133 {
134 *it = ptr;
135 return ptr;
136 }
137 }
138 dataVector.emplace_back(ptr);
139 return ptr;
James Feistd58879a2019-09-11 11:26:07 -0700140}
141
James Feist8f2710a2018-05-09 17:18:55 -0700142// writes output files to persist data
James Feista465ccc2019-02-08 12:51:01 -0800143bool writeJsonFiles(const nlohmann::json& systemConfiguration)
James Feist1b2e2242018-01-30 13:45:19 -0800144{
James Feist1df06a42019-04-11 14:23:04 -0700145 std::filesystem::create_directory(configurationOutDir);
146 std::ofstream output(currentConfiguration);
James Feistbb43d022018-06-12 15:44:33 -0700147 if (!output.good())
148 {
149 return false;
150 }
James Feist1b2e2242018-01-30 13:45:19 -0800151 output << systemConfiguration.dump(4);
152 output.close();
James Feistbb43d022018-06-12 15:44:33 -0700153 return true;
James Feist8f2710a2018-05-09 17:18:55 -0700154}
James Feist1b2e2242018-01-30 13:45:19 -0800155
James Feist97a63f12018-05-17 13:50:57 -0700156template <typename JsonType>
James Feista465ccc2019-02-08 12:51:01 -0800157bool setJsonFromPointer(const std::string& ptrStr, const JsonType& value,
158 nlohmann::json& systemConfiguration)
James Feist97a63f12018-05-17 13:50:57 -0700159{
160 try
161 {
162 nlohmann::json::json_pointer ptr(ptrStr);
James Feista465ccc2019-02-08 12:51:01 -0800163 nlohmann::json& ref = systemConfiguration[ptr];
James Feist97a63f12018-05-17 13:50:57 -0700164 ref = value;
165 return true;
166 }
James Feist98132792019-07-09 13:29:09 -0700167 catch (const std::out_of_range&)
James Feist97a63f12018-05-17 13:50:57 -0700168 {
169 return false;
170 }
171}
James Feistbb43d022018-06-12 15:44:33 -0700172
James Feistebcc26b2019-03-22 12:30:43 -0700173// template function to add array as dbus property
174template <typename PropertyType>
175void addArrayToDbus(const std::string& name, const nlohmann::json& array,
176 sdbusplus::asio::dbus_interface* iface,
177 sdbusplus::asio::PropertyPermission permission,
178 nlohmann::json& systemConfiguration,
179 const std::string& jsonPointerString)
180{
181 std::vector<PropertyType> values;
182 for (const auto& property : array)
183 {
184 auto ptr = property.get_ptr<const PropertyType*>();
185 if (ptr != nullptr)
186 {
187 values.emplace_back(*ptr);
188 }
189 }
190
191 if (permission == sdbusplus::asio::PropertyPermission::readOnly)
192 {
193 iface->register_property(name, values);
194 }
195 else
196 {
197 iface->register_property(
198 name, values,
199 [&systemConfiguration,
200 jsonPointerString{std::string(jsonPointerString)}](
201 const std::vector<PropertyType>& newVal,
202 std::vector<PropertyType>& val) {
Patrick Williamsdf190612023-05-10 07:51:34 -0500203 val = newVal;
204 if (!setJsonFromPointer(jsonPointerString, val,
205 systemConfiguration))
206 {
207 std::cerr << "error setting json field\n";
208 return -1;
209 }
210 if (!writeJsonFiles(systemConfiguration))
211 {
212 std::cerr << "error setting json file\n";
213 return -1;
214 }
215 return 1;
Patrick Williamsb9dd7f82023-10-20 11:20:11 -0500216 });
James Feistebcc26b2019-03-22 12:30:43 -0700217 }
218}
219
James Feistbb43d022018-06-12 15:44:33 -0700220template <typename PropertyType>
Ed Tanous3013fb42022-07-09 08:27:06 -0700221void addProperty(const std::string& name, const PropertyType& value,
James Feista465ccc2019-02-08 12:51:01 -0800222 sdbusplus::asio::dbus_interface* iface,
223 nlohmann::json& systemConfiguration,
224 const std::string& jsonPointerString,
James Feistbb43d022018-06-12 15:44:33 -0700225 sdbusplus::asio::PropertyPermission permission)
226{
227 if (permission == sdbusplus::asio::PropertyPermission::readOnly)
228 {
Ed Tanous3013fb42022-07-09 08:27:06 -0700229 iface->register_property(name, value);
James Feistbb43d022018-06-12 15:44:33 -0700230 return;
231 }
James Feist68500ff2018-08-08 15:40:42 -0700232 iface->register_property(
Ed Tanous3013fb42022-07-09 08:27:06 -0700233 name, value,
James Feist68500ff2018-08-08 15:40:42 -0700234 [&systemConfiguration,
235 jsonPointerString{std::string(jsonPointerString)}](
James Feista465ccc2019-02-08 12:51:01 -0800236 const PropertyType& newVal, PropertyType& val) {
Patrick Williamsdf190612023-05-10 07:51:34 -0500237 val = newVal;
238 if (!setJsonFromPointer(jsonPointerString, val, systemConfiguration))
239 {
240 std::cerr << "error setting json field\n";
241 return -1;
242 }
243 if (!writeJsonFiles(systemConfiguration))
244 {
245 std::cerr << "error setting json file\n";
246 return -1;
247 }
248 return 1;
Patrick Williamsb9dd7f82023-10-20 11:20:11 -0500249 });
James Feistc6248a52018-08-14 10:09:45 -0700250}
251
252void createDeleteObjectMethod(
James Feista465ccc2019-02-08 12:51:01 -0800253 const std::string& jsonPointerPath,
254 const std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
255 sdbusplus::asio::object_server& objServer,
256 nlohmann::json& systemConfiguration)
James Feistc6248a52018-08-14 10:09:45 -0700257{
258 std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface;
Patrick Williamsdf190612023-05-10 07:51:34 -0500259 iface->register_method("Delete",
260 [&objServer, &systemConfiguration, interface,
261 jsonPointerPath{std::string(jsonPointerPath)}]() {
262 std::shared_ptr<sdbusplus::asio::dbus_interface> dbusInterface =
263 interface.lock();
264 if (!dbusInterface)
265 {
266 // this technically can't happen as the pointer is pointing to
267 // us
268 throw DBusInternalError();
269 }
270 nlohmann::json::json_pointer ptr(jsonPointerPath);
271 systemConfiguration[ptr] = nullptr;
James Feistc6248a52018-08-14 10:09:45 -0700272
Patrick Williamsdf190612023-05-10 07:51:34 -0500273 // todo(james): dig through sdbusplus to find out why we can't
274 // delete it in a method call
275 boost::asio::post(io, [&objServer, dbusInterface]() mutable {
276 objServer.remove_interface(dbusInterface);
James Feist68500ff2018-08-08 15:40:42 -0700277 });
Patrick Williamsdf190612023-05-10 07:51:34 -0500278
279 if (!writeJsonFiles(systemConfiguration))
280 {
281 std::cerr << "error setting json file\n";
282 throw DBusInternalError();
283 }
284 });
James Feistbb43d022018-06-12 15:44:33 -0700285}
286
James Feist1b2e2242018-01-30 13:45:19 -0800287// adds simple json types to interface's properties
James Feistbb43d022018-06-12 15:44:33 -0700288void populateInterfaceFromJson(
James Feista465ccc2019-02-08 12:51:01 -0800289 nlohmann::json& systemConfiguration, const std::string& jsonPointerPath,
290 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
291 nlohmann::json& dict, sdbusplus::asio::object_server& objServer,
James Feistbb43d022018-06-12 15:44:33 -0700292 sdbusplus::asio::PropertyPermission permission =
293 sdbusplus::asio::PropertyPermission::readOnly)
James Feist1b2e2242018-01-30 13:45:19 -0800294{
Patrick Williams2594d362022-09-28 06:46:24 -0500295 for (const auto& [key, value] : dict.items())
James Feist1b2e2242018-01-30 13:45:19 -0800296 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030297 auto type = value.type();
James Feist8f2710a2018-05-09 17:18:55 -0700298 bool array = false;
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030299 if (value.type() == nlohmann::json::value_t::array)
James Feist8f2710a2018-05-09 17:18:55 -0700300 {
301 array = true;
Ed Tanous3013fb42022-07-09 08:27:06 -0700302 if (value.empty())
James Feist8f2710a2018-05-09 17:18:55 -0700303 {
304 continue;
305 }
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030306 type = value[0].type();
James Feist8f2710a2018-05-09 17:18:55 -0700307 bool isLegal = true;
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030308 for (const auto& arrayItem : value)
James Feist8f2710a2018-05-09 17:18:55 -0700309 {
310 if (arrayItem.type() != type)
311 {
312 isLegal = false;
313 break;
314 }
315 }
316 if (!isLegal)
317 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030318 std::cerr << "dbus format error" << value << "\n";
James Feist8f2710a2018-05-09 17:18:55 -0700319 continue;
320 }
James Feista218ddb2019-04-11 14:01:31 -0700321 }
322 if (type == nlohmann::json::value_t::object)
323 {
324 continue; // handled elsewhere
James Feist8f2710a2018-05-09 17:18:55 -0700325 }
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030326
327 std::string path = jsonPointerPath;
328 path.append("/").append(key);
James Feistbb43d022018-06-12 15:44:33 -0700329 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
330 {
331 // all setable numbers are doubles as it is difficult to always
332 // create a configuration file with all whole numbers as decimals
333 // i.e. 1.0
James Feistebcc26b2019-03-22 12:30:43 -0700334 if (array)
335 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030336 if (value[0].is_number())
James Feistebcc26b2019-03-22 12:30:43 -0700337 {
338 type = nlohmann::json::value_t::number_float;
339 }
340 }
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030341 else if (value.is_number())
James Feistbb43d022018-06-12 15:44:33 -0700342 {
343 type = nlohmann::json::value_t::number_float;
344 }
345 }
346
James Feist8f2710a2018-05-09 17:18:55 -0700347 switch (type)
James Feist1b2e2242018-01-30 13:45:19 -0800348 {
James Feist9eb0b582018-04-27 12:15:46 -0700349 case (nlohmann::json::value_t::boolean):
350 {
James Feist8f2710a2018-05-09 17:18:55 -0700351 if (array)
352 {
353 // todo: array of bool isn't detected correctly by
354 // sdbusplus, change it to numbers
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030355 addArrayToDbus<uint64_t>(key, value, iface.get(),
356 permission, systemConfiguration,
357 path);
James Feist8f2710a2018-05-09 17:18:55 -0700358 }
James Feistbb43d022018-06-12 15:44:33 -0700359
James Feist97a63f12018-05-17 13:50:57 -0700360 else
361 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030362 addProperty(key, value.get<bool>(), iface.get(),
363 systemConfiguration, path, permission);
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_integer):
368 {
James Feist8f2710a2018-05-09 17:18:55 -0700369 if (array)
370 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030371 addArrayToDbus<int64_t>(key, value, iface.get(), permission,
Andrew Jeffery029ee282022-03-25 13:11:36 +1030372 systemConfiguration, path);
James Feist97a63f12018-05-17 13:50:57 -0700373 }
374 else
375 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030376 addProperty(key, value.get<int64_t>(), iface.get(),
377 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_unsigned):
383 {
James Feist8f2710a2018-05-09 17:18:55 -0700384 if (array)
385 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030386 addArrayToDbus<uint64_t>(key, value, iface.get(),
387 permission, systemConfiguration,
388 path);
James Feist97a63f12018-05-17 13:50:57 -0700389 }
390 else
391 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030392 addProperty(key, value.get<uint64_t>(), iface.get(),
Andrew Jeffery029ee282022-03-25 13:11:36 +1030393 systemConfiguration, path,
James Feistbb43d022018-06-12 15:44:33 -0700394 sdbusplus::asio::PropertyPermission::readOnly);
James Feist97a63f12018-05-17 13:50:57 -0700395 }
James Feist9eb0b582018-04-27 12:15:46 -0700396 break;
397 }
398 case (nlohmann::json::value_t::number_float):
399 {
James Feist8f2710a2018-05-09 17:18:55 -0700400 if (array)
401 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030402 addArrayToDbus<double>(key, value, iface.get(), permission,
Andrew Jeffery029ee282022-03-25 13:11:36 +1030403 systemConfiguration, path);
James Feist8f2710a2018-05-09 17:18:55 -0700404 }
James Feistbb43d022018-06-12 15:44:33 -0700405
James Feist97a63f12018-05-17 13:50:57 -0700406 else
407 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030408 addProperty(key, value.get<double>(), iface.get(),
409 systemConfiguration, path, permission);
James Feist97a63f12018-05-17 13:50:57 -0700410 }
James Feist9eb0b582018-04-27 12:15:46 -0700411 break;
412 }
413 case (nlohmann::json::value_t::string):
414 {
James Feist8f2710a2018-05-09 17:18:55 -0700415 if (array)
416 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030417 addArrayToDbus<std::string>(key, value, iface.get(),
418 permission, systemConfiguration,
419 path);
James Feist97a63f12018-05-17 13:50:57 -0700420 }
421 else
422 {
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030423 addProperty(key, value.get<std::string>(), iface.get(),
424 systemConfiguration, path, permission);
James Feist97a63f12018-05-17 13:50:57 -0700425 }
James Feist9eb0b582018-04-27 12:15:46 -0700426 break;
427 }
James Feist0eb40352019-04-09 14:44:04 -0700428 default:
429 {
James Feista218ddb2019-04-11 14:01:31 -0700430 std::cerr << "Unexpected json type in system configuration "
Andrew Jeffery65ea4502022-03-25 13:15:34 +1030431 << key << ": " << value.type_name() << "\n";
James Feist0eb40352019-04-09 14:44:04 -0700432 break;
433 }
James Feist1b2e2242018-01-30 13:45:19 -0800434 }
435 }
James Feistc6248a52018-08-14 10:09:45 -0700436 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
437 {
438 createDeleteObjectMethod(jsonPointerPath, iface, objServer,
439 systemConfiguration);
440 }
John Edward Broadbentd97c6312023-10-26 20:32:07 +0000441 tryIfaceInitialize(iface);
James Feist1b2e2242018-01-30 13:45:19 -0800442}
443
James Feista465ccc2019-02-08 12:51:01 -0800444sdbusplus::asio::PropertyPermission getPermission(const std::string& interface)
James Feistc6248a52018-08-14 10:09:45 -0700445{
446 return std::find(settableInterfaces.begin(), settableInterfaces.end(),
447 interface) != settableInterfaces.end()
448 ? sdbusplus::asio::PropertyPermission::readWrite
449 : sdbusplus::asio::PropertyPermission::readOnly;
450}
451
James Feista465ccc2019-02-08 12:51:01 -0800452void createAddObjectMethod(const std::string& jsonPointerPath,
453 const std::string& path,
454 nlohmann::json& systemConfiguration,
James Feistd58879a2019-09-11 11:26:07 -0700455 sdbusplus::asio::object_server& objServer,
456 const std::string& board)
James Feist68500ff2018-08-08 15:40:42 -0700457{
James Feistd58879a2019-09-11 11:26:07 -0700458 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = createInterface(
459 objServer, path, "xyz.openbmc_project.AddObject", board);
James Feist68500ff2018-08-08 15:40:42 -0700460
461 iface->register_method(
462 "AddObject",
463 [&systemConfiguration, &objServer,
James Feistd58879a2019-09-11 11:26:07 -0700464 jsonPointerPath{std::string(jsonPointerPath)}, path{std::string(path)},
465 board](const boost::container::flat_map<std::string, JsonVariantType>&
466 data) {
Patrick Williamsdf190612023-05-10 07:51:34 -0500467 nlohmann::json::json_pointer ptr(jsonPointerPath);
468 nlohmann::json& base = systemConfiguration[ptr];
469 auto findExposes = base.find("Exposes");
James Feist68500ff2018-08-08 15:40:42 -0700470
Patrick Williamsdf190612023-05-10 07:51:34 -0500471 if (findExposes == base.end())
472 {
473 throw std::invalid_argument("Entity must have children.");
474 }
475
476 // this will throw invalid-argument to sdbusplus if invalid json
477 nlohmann::json newData{};
478 for (const auto& item : data)
479 {
480 nlohmann::json& newJson = newData[item.first];
481 std::visit(
482 [&newJson](auto&& val) {
483 newJson = std::forward<decltype(val)>(val);
Patrick Williamsb9dd7f82023-10-20 11:20:11 -0500484 },
Patrick Williamsdf190612023-05-10 07:51:34 -0500485 item.second);
486 }
487
488 auto findName = newData.find("Name");
489 auto findType = newData.find("Type");
490 if (findName == newData.end() || findType == newData.end())
491 {
492 throw std::invalid_argument("AddObject missing Name or Type");
493 }
494 const std::string* type = findType->get_ptr<const std::string*>();
495 const std::string* name = findName->get_ptr<const std::string*>();
496 if (type == nullptr || name == nullptr)
497 {
498 throw std::invalid_argument("Type and Name must be a string.");
499 }
500
501 bool foundNull = false;
502 size_t lastIndex = 0;
503 // we add in the "exposes"
504 for (const auto& expose : *findExposes)
505 {
506 if (expose.is_null())
James Feist68500ff2018-08-08 15:40:42 -0700507 {
Patrick Williamsdf190612023-05-10 07:51:34 -0500508 foundNull = true;
509 continue;
James Feist68500ff2018-08-08 15:40:42 -0700510 }
511
Patrick Williamsdf190612023-05-10 07:51:34 -0500512 if (expose["Name"] == *name && expose["Type"] == *type)
James Feist68500ff2018-08-08 15:40:42 -0700513 {
514 throw std::invalid_argument(
Patrick Williamsdf190612023-05-10 07:51:34 -0500515 "Field already in JSON, not adding");
James Feist68500ff2018-08-08 15:40:42 -0700516 }
Patrick Williamsdf190612023-05-10 07:51:34 -0500517
James Feist02d2b932020-02-06 16:28:48 -0800518 if (foundNull)
519 {
Patrick Williamsdf190612023-05-10 07:51:34 -0500520 continue;
James Feist02d2b932020-02-06 16:28:48 -0800521 }
James Feist68500ff2018-08-08 15:40:42 -0700522
Patrick Williamsdf190612023-05-10 07:51:34 -0500523 lastIndex++;
524 }
James Feistd58879a2019-09-11 11:26:07 -0700525
Patrick Williamsdf190612023-05-10 07:51:34 -0500526 std::ifstream schemaFile(std::string(schemaDirectory) + "/" +
527 boost::to_lower_copy(*type) + ".json");
528 // todo(james) we might want to also make a list of 'can add'
529 // interfaces but for now I think the assumption if there is a
530 // schema avaliable that it is allowed to update is fine
531 if (!schemaFile.good())
532 {
533 throw std::invalid_argument(
534 "No schema avaliable, cannot validate.");
535 }
536 nlohmann::json schema = nlohmann::json::parse(schemaFile, nullptr,
Potin Lai0f3a4d92023-12-05 00:13:55 +0800537 false, true);
Patrick Williamsdf190612023-05-10 07:51:34 -0500538 if (schema.is_discarded())
539 {
540 std::cerr << "Schema not legal" << *type << ".json\n";
541 throw DBusInternalError();
542 }
543 if (!validateJson(schema, newData))
544 {
545 throw std::invalid_argument("Data does not match schema");
546 }
547 if (foundNull)
548 {
549 findExposes->at(lastIndex) = newData;
550 }
551 else
552 {
553 findExposes->push_back(newData);
554 }
555 if (!writeJsonFiles(systemConfiguration))
556 {
557 std::cerr << "Error writing json files\n";
558 throw DBusInternalError();
559 }
560 std::string dbusName = *name;
561
562 std::regex_replace(dbusName.begin(), dbusName.begin(), dbusName.end(),
563 illegalDbusMemberRegex, "_");
564
565 std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
566 createInterface(objServer, path + "/" + dbusName,
567 "xyz.openbmc_project.Configuration." + *type, board,
568 true);
569 // permission is read-write, as since we just created it, must be
570 // runtime modifiable
571 populateInterfaceFromJson(
572 systemConfiguration,
573 jsonPointerPath + "/Exposes/" + std::to_string(lastIndex),
574 interface, newData, objServer,
575 sdbusplus::asio::PropertyPermission::readWrite);
Patrick Williamsb9dd7f82023-10-20 11:20:11 -0500576 });
John Edward Broadbentd97c6312023-10-26 20:32:07 +0000577 tryIfaceInitialize(iface);
James Feist68500ff2018-08-08 15:40:42 -0700578}
579
James Feista465ccc2019-02-08 12:51:01 -0800580void postToDbus(const nlohmann::json& newConfiguration,
581 nlohmann::json& systemConfiguration,
582 sdbusplus::asio::object_server& objServer)
James Feist75fdeeb2018-02-20 14:26:16 -0800583
James Feist1b2e2242018-01-30 13:45:19 -0800584{
Matt Spinler6eb60972023-08-14 16:36:20 -0500585 std::map<std::string, std::string> newBoards; // path -> name
Benjamin Fairca2eb042022-09-13 06:40:42 +0000586
James Feist97a63f12018-05-17 13:50:57 -0700587 // iterate through boards
Patrick Williams2594d362022-09-28 06:46:24 -0500588 for (const auto& [boardId, boardConfig] : newConfiguration.items())
James Feist1b2e2242018-01-30 13:45:19 -0800589 {
Matt Spinler3d1909a2023-08-10 16:39:44 -0500590 std::string boardName = boardConfig["Name"];
591 std::string boardNameOrig = boardConfig["Name"];
Andrew Jeffery13132df2022-03-25 13:29:41 +1030592 std::string jsonPointerPath = "/" + boardId;
James Feist97a63f12018-05-17 13:50:57 -0700593 // loop through newConfiguration, but use values from system
594 // configuration to be able to modify via dbus later
Andrew Jeffery13132df2022-03-25 13:29:41 +1030595 auto boardValues = systemConfiguration[boardId];
James Feistd63d18a2018-07-19 15:23:45 -0700596 auto findBoardType = boardValues.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -0800597 std::string boardType;
598 if (findBoardType != boardValues.end() &&
599 findBoardType->type() == nlohmann::json::value_t::string)
600 {
601 boardType = findBoardType->get<std::string>();
602 std::regex_replace(boardType.begin(), boardType.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800603 boardType.end(), illegalDbusMemberRegex, "_");
James Feist1b2e2242018-01-30 13:45:19 -0800604 }
605 else
606 {
Matt Spinler3d1909a2023-08-10 16:39:44 -0500607 std::cerr << "Unable to find type for " << boardName
James Feist1b2e2242018-01-30 13:45:19 -0800608 << " reverting to Chassis.\n";
609 boardType = "Chassis";
610 }
James Feist11be6672018-04-06 14:05:32 -0700611 std::string boardtypeLower = boost::algorithm::to_lower_copy(boardType);
James Feist1b2e2242018-01-30 13:45:19 -0800612
Matt Spinler3d1909a2023-08-10 16:39:44 -0500613 std::regex_replace(boardName.begin(), boardName.begin(),
614 boardName.end(), illegalDbusMemberRegex, "_");
615 std::string boardPath = "/xyz/openbmc_project/inventory/system/";
616 boardPath += boardtypeLower;
617 boardPath += "/";
618 boardPath += boardName;
James Feist1b2e2242018-01-30 13:45:19 -0800619
James Feistd58879a2019-09-11 11:26:07 -0700620 std::shared_ptr<sdbusplus::asio::dbus_interface> inventoryIface =
Matt Spinler3d1909a2023-08-10 16:39:44 -0500621 createInterface(objServer, boardPath,
622 "xyz.openbmc_project.Inventory.Item", boardName);
James Feist68500ff2018-08-08 15:40:42 -0700623
James Feistd58879a2019-09-11 11:26:07 -0700624 std::shared_ptr<sdbusplus::asio::dbus_interface> boardIface =
Matt Spinler3d1909a2023-08-10 16:39:44 -0500625 createInterface(objServer, boardPath,
James Feistd58879a2019-09-11 11:26:07 -0700626 "xyz.openbmc_project.Inventory.Item." + boardType,
Matt Spinler3d1909a2023-08-10 16:39:44 -0500627 boardNameOrig);
James Feist11be6672018-04-06 14:05:32 -0700628
Matt Spinler3d1909a2023-08-10 16:39:44 -0500629 createAddObjectMethod(jsonPointerPath, boardPath, systemConfiguration,
630 objServer, boardNameOrig);
James Feist68500ff2018-08-08 15:40:42 -0700631
James Feist97a63f12018-05-17 13:50:57 -0700632 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
James Feistc6248a52018-08-14 10:09:45 -0700633 boardIface, boardValues, objServer);
James Feist97a63f12018-05-17 13:50:57 -0700634 jsonPointerPath += "/";
635 // iterate through board properties
Patrick Williams2594d362022-09-28 06:46:24 -0500636 for (const auto& [propName, propValue] : boardValues.items())
James Feist11be6672018-04-06 14:05:32 -0700637 {
Andrew Jefferya96950d2022-03-25 13:32:46 +1030638 if (propValue.type() == nlohmann::json::value_t::object)
James Feist11be6672018-04-06 14:05:32 -0700639 {
James Feistd58879a2019-09-11 11:26:07 -0700640 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
Matt Spinler3d1909a2023-08-10 16:39:44 -0500641 createInterface(objServer, boardPath, propName,
642 boardNameOrig);
James Feistd58879a2019-09-11 11:26:07 -0700643
James Feistc6248a52018-08-14 10:09:45 -0700644 populateInterfaceFromJson(systemConfiguration,
Andrew Jefferya96950d2022-03-25 13:32:46 +1030645 jsonPointerPath + propName, iface,
646 propValue, objServer);
James Feist11be6672018-04-06 14:05:32 -0700647 }
648 }
James Feist97a63f12018-05-17 13:50:57 -0700649
James Feist1e3e6982018-08-03 16:09:28 -0700650 auto exposes = boardValues.find("Exposes");
James Feist1b2e2242018-01-30 13:45:19 -0800651 if (exposes == boardValues.end())
652 {
653 continue;
654 }
James Feist97a63f12018-05-17 13:50:57 -0700655 // iterate through exposes
James Feist1e3e6982018-08-03 16:09:28 -0700656 jsonPointerPath += "Exposes/";
James Feist97a63f12018-05-17 13:50:57 -0700657
658 // store the board level pointer so we can modify it on the way down
659 std::string jsonPointerPathBoard = jsonPointerPath;
660 size_t exposesIndex = -1;
James Feista465ccc2019-02-08 12:51:01 -0800661 for (auto& item : *exposes)
James Feist1b2e2242018-01-30 13:45:19 -0800662 {
James Feist97a63f12018-05-17 13:50:57 -0700663 exposesIndex++;
664 jsonPointerPath = jsonPointerPathBoard;
665 jsonPointerPath += std::to_string(exposesIndex);
666
James Feistd63d18a2018-07-19 15:23:45 -0700667 auto findName = item.find("Name");
James Feist1b2e2242018-01-30 13:45:19 -0800668 if (findName == item.end())
669 {
670 std::cerr << "cannot find name in field " << item << "\n";
671 continue;
672 }
James Feist1e3e6982018-08-03 16:09:28 -0700673 auto findStatus = item.find("Status");
James Feist1b2e2242018-01-30 13:45:19 -0800674 // if status is not found it is assumed to be status = 'okay'
675 if (findStatus != item.end())
676 {
677 if (*findStatus == "disabled")
678 {
679 continue;
680 }
681 }
James Feistd63d18a2018-07-19 15:23:45 -0700682 auto findType = item.find("Type");
James Feist1b2e2242018-01-30 13:45:19 -0800683 std::string itemType;
684 if (findType != item.end())
685 {
686 itemType = findType->get<std::string>();
687 std::regex_replace(itemType.begin(), itemType.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800688 itemType.end(), illegalDbusPathRegex, "_");
James Feist1b2e2242018-01-30 13:45:19 -0800689 }
690 else
691 {
692 itemType = "unknown";
693 }
694 std::string itemName = findName->get<std::string>();
695 std::regex_replace(itemName.begin(), itemName.begin(),
Ed Tanous07d467b2021-02-23 14:48:37 -0800696 itemName.end(), illegalDbusMemberRegex, "_");
Matt Spinler3d1909a2023-08-10 16:39:44 -0500697 std::string ifacePath = boardPath;
Ed Tanous07d467b2021-02-23 14:48:37 -0800698 ifacePath += "/";
699 ifacePath += itemName;
James Feistc6248a52018-08-14 10:09:45 -0700700
James Feistd58879a2019-09-11 11:26:07 -0700701 std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface =
Ed Tanous07d467b2021-02-23 14:48:37 -0800702 createInterface(objServer, ifacePath,
James Feistd58879a2019-09-11 11:26:07 -0700703 "xyz.openbmc_project.Configuration." + itemType,
Matt Spinler3d1909a2023-08-10 16:39:44 -0500704 boardNameOrig);
James Feist1b2e2242018-01-30 13:45:19 -0800705
Sui Chen74ebe592022-09-13 10:22:03 -0700706 if (itemType == "BMC")
707 {
708 std::shared_ptr<sdbusplus::asio::dbus_interface> bmcIface =
709 createInterface(objServer, ifacePath,
710 "xyz.openbmc_project.Inventory.Item.Bmc",
Matt Spinler3d1909a2023-08-10 16:39:44 -0500711 boardNameOrig);
Sui Chen74ebe592022-09-13 10:22:03 -0700712 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
713 bmcIface, item, objServer,
714 getPermission(itemType));
715 }
Edward Leeeb587b42023-03-08 18:59:04 +0000716 else if (itemType == "System")
717 {
718 std::shared_ptr<sdbusplus::asio::dbus_interface> systemIface =
719 createInterface(objServer, ifacePath,
720 "xyz.openbmc_project.Inventory.Item.System",
Matt Spinler3d1909a2023-08-10 16:39:44 -0500721 boardNameOrig);
Edward Leeeb587b42023-03-08 18:59:04 +0000722 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
723 systemIface, item, objServer,
724 getPermission(itemType));
725 }
Sui Chen74ebe592022-09-13 10:22:03 -0700726
James Feist97a63f12018-05-17 13:50:57 -0700727 populateInterfaceFromJson(systemConfiguration, jsonPointerPath,
James Feistc6248a52018-08-14 10:09:45 -0700728 itemIface, item, objServer,
729 getPermission(itemType));
James Feist1b2e2242018-01-30 13:45:19 -0800730
Patrick Williams2594d362022-09-28 06:46:24 -0500731 for (const auto& [name, config] : item.items())
James Feist1b2e2242018-01-30 13:45:19 -0800732 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030733 jsonPointerPath = jsonPointerPathBoard;
734 jsonPointerPath.append(std::to_string(exposesIndex))
735 .append("/")
736 .append(name);
737 if (config.type() == nlohmann::json::value_t::object)
James Feist1b2e2242018-01-30 13:45:19 -0800738 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030739 std::string ifaceName =
740 "xyz.openbmc_project.Configuration.";
741 ifaceName.append(itemType).append(".").append(name);
James Feist97a63f12018-05-17 13:50:57 -0700742
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030743 std::shared_ptr<sdbusplus::asio::dbus_interface>
744 objectIface = createInterface(objServer, ifacePath,
Matt Spinler3d1909a2023-08-10 16:39:44 -0500745 ifaceName, boardNameOrig);
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030746
747 populateInterfaceFromJson(
748 systemConfiguration, jsonPointerPath, objectIface,
749 config, objServer, getPermission(name));
James Feist1b2e2242018-01-30 13:45:19 -0800750 }
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030751 else if (config.type() == nlohmann::json::value_t::array)
James Feist1b2e2242018-01-30 13:45:19 -0800752 {
753 size_t index = 0;
Ed Tanous3013fb42022-07-09 08:27:06 -0700754 if (config.empty())
James Feist1b2e2242018-01-30 13:45:19 -0800755 {
James Feist8f2710a2018-05-09 17:18:55 -0700756 continue;
757 }
758 bool isLegal = true;
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030759 auto type = config[0].type();
James Feist8f2710a2018-05-09 17:18:55 -0700760 if (type != nlohmann::json::value_t::object)
761 {
762 continue;
763 }
764
765 // verify legal json
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030766 for (const auto& arrayItem : config)
James Feist8f2710a2018-05-09 17:18:55 -0700767 {
768 if (arrayItem.type() != type)
James Feist1b2e2242018-01-30 13:45:19 -0800769 {
James Feist8f2710a2018-05-09 17:18:55 -0700770 isLegal = false;
James Feist1b2e2242018-01-30 13:45:19 -0800771 break;
772 }
James Feist8f2710a2018-05-09 17:18:55 -0700773 }
774 if (!isLegal)
775 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030776 std::cerr << "dbus format error" << config << "\n";
James Feist8f2710a2018-05-09 17:18:55 -0700777 break;
778 }
779
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030780 for (auto& arrayItem : config)
James Feist8f2710a2018-05-09 17:18:55 -0700781 {
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030782 std::string ifaceName =
783 "xyz.openbmc_project.Configuration.";
784 ifaceName.append(itemType).append(".").append(name);
785 ifaceName.append(std::to_string(index));
James Feist97a63f12018-05-17 13:50:57 -0700786
James Feistd58879a2019-09-11 11:26:07 -0700787 std::shared_ptr<sdbusplus::asio::dbus_interface>
788 objectIface = createInterface(
Matt Spinler3d1909a2023-08-10 16:39:44 -0500789 objServer, ifacePath, ifaceName, boardNameOrig);
James Feistd58879a2019-09-11 11:26:07 -0700790
James Feistc6248a52018-08-14 10:09:45 -0700791 populateInterfaceFromJson(
792 systemConfiguration,
793 jsonPointerPath + "/" + std::to_string(index),
794 objectIface, arrayItem, objServer,
Andrew Jeffery5a6379c2022-03-25 13:25:31 +1030795 getPermission(name));
James Feistbb43d022018-06-12 15:44:33 -0700796 index++;
James Feist1b2e2242018-01-30 13:45:19 -0800797 }
798 }
799 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000800
Matt Spinler6eb60972023-08-14 16:36:20 -0500801 topology.addBoard(boardPath, boardType, boardNameOrig, item);
James Feist1b2e2242018-01-30 13:45:19 -0800802 }
Matt Spinler6eb60972023-08-14 16:36:20 -0500803
804 newBoards.emplace(boardPath, boardNameOrig);
James Feist1b2e2242018-01-30 13:45:19 -0800805 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000806
Matt Spinler6eb60972023-08-14 16:36:20 -0500807 for (const auto& [assocPath, assocPropValue] :
808 topology.getAssocs(newBoards))
Benjamin Fairca2eb042022-09-13 06:40:42 +0000809 {
Matt Spinler6eb60972023-08-14 16:36:20 -0500810 auto findBoard = newBoards.find(assocPath);
811 if (findBoard == newBoards.end())
812 {
813 continue;
814 }
Benjamin Fairca2eb042022-09-13 06:40:42 +0000815
Matt Spinler6eb60972023-08-14 16:36:20 -0500816 auto ifacePtr = createInterface(
817 objServer, assocPath, "xyz.openbmc_project.Association.Definitions",
818 findBoard->second);
819
820 ifacePtr->register_property("Associations", assocPropValue);
John Edward Broadbentd97c6312023-10-26 20:32:07 +0000821 tryIfaceInitialize(ifacePtr);
Benjamin Fairca2eb042022-09-13 06:40:42 +0000822 }
James Feist1b2e2242018-01-30 13:45:19 -0800823}
824
James Feist8f2710a2018-05-09 17:18:55 -0700825// reads json files out of the filesystem
Andrew Jefferyf3311792022-03-29 22:09:00 +1030826bool loadConfigurations(std::list<nlohmann::json>& configurations)
James Feist3cb5fec2018-01-23 14:41:51 -0800827{
828 // find configuration files
Ed Tanous072e25d2018-12-16 21:45:20 -0800829 std::vector<std::filesystem::path> jsonPaths;
Andrew Jefferya9c58922021-06-01 09:28:59 +0930830 if (!findFiles(
831 std::vector<std::filesystem::path>{configurationDirectory,
832 hostConfigurationDirectory},
833 R"(.*\.json)", jsonPaths))
James Feist3cb5fec2018-01-23 14:41:51 -0800834 {
835 std::cerr << "Unable to find any configuration files in "
James Feistb4383f42018-08-06 16:54:10 -0700836 << configurationDirectory << "\n";
James Feist75fdeeb2018-02-20 14:26:16 -0800837 return false;
James Feist3cb5fec2018-01-23 14:41:51 -0800838 }
James Feistb4383f42018-08-06 16:54:10 -0700839
840 std::ifstream schemaStream(std::string(schemaDirectory) + "/" +
841 globalSchema);
842 if (!schemaStream.good())
843 {
844 std::cerr
845 << "Cannot open schema file, cannot validate JSON, exiting\n\n";
846 std::exit(EXIT_FAILURE);
Ed Tanous072e25d2018-12-16 21:45:20 -0800847 return false;
James Feistb4383f42018-08-06 16:54:10 -0700848 }
Potin Lai0f3a4d92023-12-05 00:13:55 +0800849 nlohmann::json schema = nlohmann::json::parse(schemaStream, nullptr, false,
850 true);
James Feistb4383f42018-08-06 16:54:10 -0700851 if (schema.is_discarded())
852 {
853 std::cerr
854 << "Illegal schema file detected, cannot validate JSON, exiting\n";
855 std::exit(EXIT_FAILURE);
Ed Tanous072e25d2018-12-16 21:45:20 -0800856 return false;
James Feistb4383f42018-08-06 16:54:10 -0700857 }
858
James Feista465ccc2019-02-08 12:51:01 -0800859 for (auto& jsonPath : jsonPaths)
James Feist3cb5fec2018-01-23 14:41:51 -0800860 {
861 std::ifstream jsonStream(jsonPath.c_str());
862 if (!jsonStream.good())
863 {
864 std::cerr << "unable to open " << jsonPath.string() << "\n";
865 continue;
866 }
Potin Lai0f3a4d92023-12-05 00:13:55 +0800867 auto data = nlohmann::json::parse(jsonStream, nullptr, false, true);
James Feist3cb5fec2018-01-23 14:41:51 -0800868 if (data.is_discarded())
869 {
870 std::cerr << "syntax error in " << jsonPath.string() << "\n";
871 continue;
872 }
James Feist8da99192019-01-24 08:20:16 -0800873 /*
874 * todo(james): reenable this once less things are in flight
875 *
James Feistb4383f42018-08-06 16:54:10 -0700876 if (!validateJson(schema, data))
877 {
878 std::cerr << "Error validating " << jsonPath.string() << "\n";
879 continue;
880 }
James Feist8da99192019-01-24 08:20:16 -0800881 */
James Feistb4383f42018-08-06 16:54:10 -0700882
James Feist3cb5fec2018-01-23 14:41:51 -0800883 if (data.type() == nlohmann::json::value_t::array)
884 {
James Feista465ccc2019-02-08 12:51:01 -0800885 for (auto& d : data)
James Feist3cb5fec2018-01-23 14:41:51 -0800886 {
887 configurations.emplace_back(d);
888 }
889 }
890 else
891 {
892 configurations.emplace_back(data);
893 }
894 }
Ed Tanous072e25d2018-12-16 21:45:20 -0800895 return true;
James Feist75fdeeb2018-02-20 14:26:16 -0800896}
James Feist3cb5fec2018-01-23 14:41:51 -0800897
Andrew Jeffery55192932022-03-24 12:29:27 +1030898static bool deviceRequiresPowerOn(const nlohmann::json& entity)
899{
900 auto powerState = entity.find("PowerState");
Andrew Jefferyb6209442022-03-24 12:36:20 +1030901 if (powerState == entity.end())
Andrew Jeffery55192932022-03-24 12:29:27 +1030902 {
Andrew Jefferyb6209442022-03-24 12:36:20 +1030903 return false;
Andrew Jeffery55192932022-03-24 12:29:27 +1030904 }
905
Ed Tanous3013fb42022-07-09 08:27:06 -0700906 const auto* ptr = powerState->get_ptr<const std::string*>();
907 if (ptr == nullptr)
Andrew Jefferyb6209442022-03-24 12:36:20 +1030908 {
909 return false;
910 }
911
912 return *ptr == "On" || *ptr == "BiosPost";
Andrew Jeffery55192932022-03-24 12:29:27 +1030913}
914
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030915static void pruneDevice(const nlohmann::json& systemConfiguration,
916 const bool powerOff, const bool scannedPowerOff,
917 const std::string& name, const nlohmann::json& device)
918{
919 if (systemConfiguration.contains(name))
920 {
921 return;
922 }
923
Andrew Jeffery4db38bc2022-03-24 13:42:41 +1030924 if (deviceRequiresPowerOn(device) && (powerOff || scannedPowerOff))
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030925 {
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030926 return;
927 }
928
929 logDeviceRemoved(device);
930}
931
James Feistb1728ca2020-04-30 15:40:55 -0700932void startRemovedTimer(boost::asio::steady_timer& timer,
James Feist1df06a42019-04-11 14:23:04 -0700933 nlohmann::json& systemConfiguration)
934{
935 static bool scannedPowerOff = false;
936 static bool scannedPowerOn = false;
937
James Feistfb00f392019-06-25 14:16:48 -0700938 if (systemConfiguration.empty() || lastJson.empty())
939 {
940 return; // not ready yet
941 }
James Feist1df06a42019-04-11 14:23:04 -0700942 if (scannedPowerOn)
943 {
944 return;
945 }
946
947 if (!isPowerOn() && scannedPowerOff)
948 {
949 return;
950 }
951
James Feistb1728ca2020-04-30 15:40:55 -0700952 timer.expires_after(std::chrono::seconds(10));
Andrew Jeffery27a1cfb2022-03-24 12:31:53 +1030953 timer.async_wait(
954 [&systemConfiguration](const boost::system::error_code& ec) {
Patrick Williamsdf190612023-05-10 07:51:34 -0500955 if (ec == boost::asio::error::operation_aborted)
956 {
957 return;
958 }
Andrew Jeffery27a1cfb2022-03-24 12:31:53 +1030959
Patrick Williamsdf190612023-05-10 07:51:34 -0500960 bool powerOff = !isPowerOn();
961 for (const auto& [name, device] : lastJson.items())
962 {
963 pruneDevice(systemConfiguration, powerOff, scannedPowerOff, name,
964 device);
965 }
Andrew Jeffery89ec3522022-03-24 13:30:41 +1030966
Patrick Williamsdf190612023-05-10 07:51:34 -0500967 scannedPowerOff = true;
968 if (!powerOff)
969 {
970 scannedPowerOn = true;
971 }
972 });
James Feist1df06a42019-04-11 14:23:04 -0700973}
974
Andrew Jeffery2f750d22022-03-24 14:32:57 +1030975static std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>&
976 getDeviceInterfaces(const nlohmann::json& device)
977{
978 return inventory[device["Name"].get<std::string>()];
979}
980
Andrew Jeffery0d0c1462022-03-24 15:26:39 +1030981static void pruneConfiguration(nlohmann::json& systemConfiguration,
982 sdbusplus::asio::object_server& objServer,
983 bool powerOff, const std::string& name,
984 const nlohmann::json& device)
985{
986 if (powerOff && deviceRequiresPowerOn(device))
987 {
988 // power not on yet, don't know if it's there or not
989 return;
990 }
991
992 auto& ifaces = getDeviceInterfaces(device);
993 for (auto& iface : ifaces)
994 {
995 auto sharedPtr = iface.lock();
996 if (!!sharedPtr)
997 {
998 objServer.remove_interface(sharedPtr);
999 }
1000 }
1001
1002 ifaces.clear();
1003 systemConfiguration.erase(name);
Matt Spinler6eb60972023-08-14 16:36:20 -05001004 topology.remove(device["Name"].get<std::string>());
Andrew Jeffery0d0c1462022-03-24 15:26:39 +10301005 logDeviceRemoved(device);
1006}
1007
Andrew Jeffery7c4cbbe2022-03-24 15:27:10 +10301008static void deriveNewConfiguration(const nlohmann::json& oldConfiguration,
1009 nlohmann::json& newConfiguration)
1010{
1011 for (auto it = newConfiguration.begin(); it != newConfiguration.end();)
1012 {
1013 auto findKey = oldConfiguration.find(it.key());
1014 if (findKey != oldConfiguration.end())
1015 {
1016 it = newConfiguration.erase(it);
1017 }
1018 else
1019 {
1020 it++;
1021 }
1022 }
1023}
1024
Andrew Jefferye35d0ac2022-03-24 15:53:13 +10301025static void publishNewConfiguration(
1026 const size_t& instance, const size_t count,
1027 boost::asio::steady_timer& timer, nlohmann::json& systemConfiguration,
1028 // Gerrit discussion:
1029 // https://gerrit.openbmc-project.xyz/c/openbmc/entity-manager/+/52316/6
1030 //
1031 // Discord discussion:
1032 // https://discord.com/channels/775381525260664832/867820390406422538/958048437729910854
1033 //
1034 // NOLINTNEXTLINE(performance-unnecessary-value-param)
1035 const nlohmann::json newConfiguration,
1036 sdbusplus::asio::object_server& objServer)
1037{
1038 loadOverlays(newConfiguration);
1039
Ed Tanous49a888c2023-03-06 13:44:51 -08001040 boost::asio::post(io, [systemConfiguration]() {
Andrew Jefferye35d0ac2022-03-24 15:53:13 +10301041 if (!writeJsonFiles(systemConfiguration))
1042 {
1043 std::cerr << "Error writing json files\n";
1044 }
1045 });
1046
Ed Tanous49a888c2023-03-06 13:44:51 -08001047 boost::asio::post(io, [&instance, count, &timer, newConfiguration,
1048 &systemConfiguration, &objServer]() {
Andrew Jefferye35d0ac2022-03-24 15:53:13 +10301049 postToDbus(newConfiguration, systemConfiguration, objServer);
1050 if (count == instance)
1051 {
1052 startRemovedTimer(timer, systemConfiguration);
1053 }
1054 });
1055}
1056
James Feist8f2710a2018-05-09 17:18:55 -07001057// main properties changed entry
James Feist4dc617b2020-05-01 09:54:47 -07001058void propertiesChangedCallback(nlohmann::json& systemConfiguration,
1059 sdbusplus::asio::object_server& objServer)
James Feist8f2710a2018-05-09 17:18:55 -07001060{
James Feist2539ccd2020-05-01 16:15:08 -07001061 static bool inProgress = false;
James Feistb1728ca2020-04-30 15:40:55 -07001062 static boost::asio::steady_timer timer(io);
James Feist899e17f2019-09-13 11:46:29 -07001063 static size_t instance = 0;
1064 instance++;
1065 size_t count = instance;
James Feist1df06a42019-04-11 14:23:04 -07001066
James Feistb1728ca2020-04-30 15:40:55 -07001067 timer.expires_after(std::chrono::seconds(5));
James Feist8f2710a2018-05-09 17:18:55 -07001068
1069 // setup an async wait as we normally get flooded with new requests
James Feist4dc617b2020-05-01 09:54:47 -07001070 timer.async_wait([&systemConfiguration, &objServer,
James Feist899e17f2019-09-13 11:46:29 -07001071 count](const boost::system::error_code& ec) {
James Feist8f2710a2018-05-09 17:18:55 -07001072 if (ec == boost::asio::error::operation_aborted)
1073 {
1074 // we were cancelled
1075 return;
1076 }
Ed Tanous07d467b2021-02-23 14:48:37 -08001077 if (ec)
James Feist8f2710a2018-05-09 17:18:55 -07001078 {
1079 std::cerr << "async wait error " << ec << "\n";
1080 return;
1081 }
1082
James Feist2539ccd2020-05-01 16:15:08 -07001083 if (inProgress)
1084 {
1085 propertiesChangedCallback(systemConfiguration, objServer);
1086 return;
1087 }
1088 inProgress = true;
1089
James Feist8f2710a2018-05-09 17:18:55 -07001090 nlohmann::json oldConfiguration = systemConfiguration;
James Feist899e17f2019-09-13 11:46:29 -07001091 auto missingConfigurations = std::make_shared<nlohmann::json>();
1092 *missingConfigurations = systemConfiguration;
1093
James Feist8f2710a2018-05-09 17:18:55 -07001094 std::list<nlohmann::json> configurations;
Andrew Jefferyf3311792022-03-29 22:09:00 +10301095 if (!loadConfigurations(configurations))
James Feist8f2710a2018-05-09 17:18:55 -07001096 {
Andrew Jefferyf3311792022-03-29 22:09:00 +10301097 std::cerr << "Could not load configurations\n";
James Feist2539ccd2020-05-01 16:15:08 -07001098 inProgress = false;
James Feist8f2710a2018-05-09 17:18:55 -07001099 return;
1100 }
1101
1102 auto perfScan = std::make_shared<PerformScan>(
James Feist899e17f2019-09-13 11:46:29 -07001103 systemConfiguration, *missingConfigurations, configurations,
James Feist4dc617b2020-05-01 09:54:47 -07001104 objServer,
1105 [&systemConfiguration, &objServer, count, oldConfiguration,
1106 missingConfigurations]() {
Patrick Williamsdf190612023-05-10 07:51:34 -05001107 // this is something that since ac has been applied to the bmc
1108 // we saw, and we no longer see it
1109 bool powerOff = !isPowerOn();
1110 for (const auto& [name, device] : missingConfigurations->items())
1111 {
1112 pruneConfiguration(systemConfiguration, objServer, powerOff,
1113 name, device);
1114 }
James Feist899e17f2019-09-13 11:46:29 -07001115
Patrick Williamsdf190612023-05-10 07:51:34 -05001116 nlohmann::json newConfiguration = systemConfiguration;
Andrew Jeffery7c4cbbe2022-03-24 15:27:10 +10301117
Patrick Williamsdf190612023-05-10 07:51:34 -05001118 deriveNewConfiguration(oldConfiguration, newConfiguration);
Andrew Jeffery7c4cbbe2022-03-24 15:27:10 +10301119
Patrick Williamsdf190612023-05-10 07:51:34 -05001120 for (const auto& [_, device] : newConfiguration.items())
1121 {
1122 logDeviceAdded(device);
1123 }
James Feist899e17f2019-09-13 11:46:29 -07001124
Patrick Williamsdf190612023-05-10 07:51:34 -05001125 inProgress = false;
James Feist2539ccd2020-05-01 16:15:08 -07001126
Patrick Williamsdf190612023-05-10 07:51:34 -05001127 boost::asio::post(
1128 io, std::bind_front(publishNewConfiguration, std::ref(instance),
1129 count, std::ref(timer),
1130 std::ref(systemConfiguration),
1131 newConfiguration, std::ref(objServer)));
Patrick Williamsb9dd7f82023-10-20 11:20:11 -05001132 });
James Feist8f2710a2018-05-09 17:18:55 -07001133 perfScan->run();
1134 });
James Feist75fdeeb2018-02-20 14:26:16 -08001135}
1136
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001137// Extract the D-Bus interfaces to probe from the JSON config files.
1138static std::set<std::string> getProbeInterfaces()
1139{
1140 std::set<std::string> interfaces;
1141 std::list<nlohmann::json> configurations;
1142 if (!loadConfigurations(configurations))
1143 {
1144 return interfaces;
1145 }
1146
1147 for (auto it = configurations.begin(); it != configurations.end();)
1148 {
1149 auto findProbe = it->find("Probe");
1150 if (findProbe == it->end())
1151 {
1152 std::cerr << "configuration file missing probe:\n " << *it << "\n";
1153 it++;
1154 continue;
1155 }
1156
1157 nlohmann::json probeCommand;
1158 if ((*findProbe).type() != nlohmann::json::value_t::array)
1159 {
1160 probeCommand = nlohmann::json::array();
1161 probeCommand.push_back(*findProbe);
1162 }
1163 else
1164 {
1165 probeCommand = *findProbe;
1166 }
1167
1168 for (const nlohmann::json& probeJson : probeCommand)
1169 {
1170 const std::string* probe = probeJson.get_ptr<const std::string*>();
1171 if (probe == nullptr)
1172 {
1173 std::cerr << "Probe statement wasn't a string, can't parse";
1174 continue;
1175 }
1176 // Skip it if the probe cmd doesn't contain an interface.
1177 if (findProbeType(*probe))
1178 {
1179 continue;
1180 }
1181
1182 // syntax requires probe before first open brace
1183 auto findStart = probe->find('(');
1184 if (findStart != std::string::npos)
1185 {
1186 std::string interface = probe->substr(0, findStart);
1187 interfaces.emplace(interface);
1188 }
1189 }
1190 it++;
1191 }
1192
1193 return interfaces;
1194}
1195
1196// Check if InterfacesAdded payload contains an iface that needs probing.
1197static bool
1198 iaContainsProbeInterface(sdbusplus::message_t& msg,
1199 const std::set<std::string>& probeInterfaces)
1200{
1201 sdbusplus::message::object_path path;
1202 DBusObject interfaces;
1203 std::set<std::string> interfaceSet;
1204 std::set<std::string> intersect;
1205
1206 msg.read(path, interfaces);
1207
1208 std::for_each(interfaces.begin(), interfaces.end(),
1209 [&interfaceSet](const auto& iface) {
Patrick Williamsdf190612023-05-10 07:51:34 -05001210 interfaceSet.insert(iface.first);
1211 });
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001212
1213 std::set_intersection(interfaceSet.begin(), interfaceSet.end(),
1214 probeInterfaces.begin(), probeInterfaces.end(),
1215 std::inserter(intersect, intersect.end()));
1216 return !intersect.empty();
1217}
1218
1219// Check if InterfacesRemoved payload contains an iface that needs probing.
1220static bool
1221 irContainsProbeInterface(sdbusplus::message_t& msg,
1222 const std::set<std::string>& probeInterfaces)
1223{
1224 sdbusplus::message::object_path path;
1225 std::set<std::string> interfaces;
1226 std::set<std::string> intersect;
1227
1228 msg.read(path, interfaces);
1229
1230 std::set_intersection(interfaces.begin(), interfaces.end(),
1231 probeInterfaces.begin(), probeInterfaces.end(),
1232 std::inserter(intersect, intersect.end()));
1233 return !intersect.empty();
1234}
1235
James Feist98132792019-07-09 13:29:09 -07001236int main()
James Feist75fdeeb2018-02-20 14:26:16 -08001237{
1238 // setup connection to dbus
Ed Tanous07d467b2021-02-23 14:48:37 -08001239 systemBus = std::make_shared<sdbusplus::asio::connection>(io);
1240 systemBus->request_name("xyz.openbmc_project.EntityManager");
James Feist4131aea2018-03-09 09:47:30 -08001241
Nan Zhoua3315672022-09-20 19:48:14 +00001242 // The EntityManager object itself doesn't expose any properties.
1243 // No need to set up ObjectManager for the |EntityManager| object.
1244 sdbusplus::asio::object_server objServer(systemBus, /*skipManager=*/true);
1245
1246 // All other objects that EntityManager currently support are under the
1247 // inventory subtree.
1248 // See the discussion at
1249 // https://discord.com/channels/775381525260664832/1018929092009144380
1250 objServer.add_manager("/xyz/openbmc_project/inventory");
James Feistfd1264a2018-05-03 12:10:00 -07001251
James Feist8f2710a2018-05-09 17:18:55 -07001252 std::shared_ptr<sdbusplus::asio::dbus_interface> entityIface =
1253 objServer.add_interface("/xyz/openbmc_project/EntityManager",
1254 "xyz.openbmc_project.EntityManager");
James Feistfd1264a2018-05-03 12:10:00 -07001255
James Feist4131aea2018-03-09 09:47:30 -08001256 // to keep reference to the match / filter objects so they don't get
1257 // destroyed
James Feist8f2710a2018-05-09 17:18:55 -07001258
1259 nlohmann::json systemConfiguration = nlohmann::json::object();
1260
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001261 std::set<std::string> probeInterfaces = getProbeInterfaces();
1262
Brad Bishopc76af0f2020-12-04 13:50:23 -05001263 // We need a poke from DBus for static providers that create all their
1264 // objects prior to claiming a well-known name, and thus don't emit any
1265 // org.freedesktop.DBus.Properties signals. Similarly if a process exits
1266 // for any reason, expected or otherwise, we'll need a poke to remove
1267 // entities from DBus.
Patrick Williams2af39222022-07-22 19:26:56 -05001268 sdbusplus::bus::match_t nameOwnerChangedMatch(
1269 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishopc76af0f2020-12-04 13:50:23 -05001270 sdbusplus::bus::match::rules::nameOwnerChanged(),
Patrick Williams7b8786f2022-10-10 10:23:37 -05001271 [&](sdbusplus::message_t& m) {
Patrick Williamsdf190612023-05-10 07:51:34 -05001272 auto [name, oldOwner,
1273 newOwner] = m.unpack<std::string, std::string, std::string>();
Patrick Williams7b8786f2022-10-10 10:23:37 -05001274
Patrick Williamsdf190612023-05-10 07:51:34 -05001275 if (name.starts_with(':'))
1276 {
1277 // We should do nothing with unique-name connections.
1278 return;
1279 }
Patrick Williams7b8786f2022-10-10 10:23:37 -05001280
Patrick Williamsdf190612023-05-10 07:51:34 -05001281 propertiesChangedCallback(systemConfiguration, objServer);
Patrick Williamsb9dd7f82023-10-20 11:20:11 -05001282 });
Brad Bishop10a8c5f2020-12-07 21:40:07 -05001283 // We also need a poke from DBus when new interfaces are created or
1284 // destroyed.
Patrick Williams2af39222022-07-22 19:26:56 -05001285 sdbusplus::bus::match_t interfacesAddedMatch(
1286 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishop10a8c5f2020-12-07 21:40:07 -05001287 sdbusplus::bus::match::rules::interfacesAdded(),
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001288 [&](sdbusplus::message_t& msg) {
Patrick Williamsdf190612023-05-10 07:51:34 -05001289 if (iaContainsProbeInterface(msg, probeInterfaces))
1290 {
1291 propertiesChangedCallback(systemConfiguration, objServer);
1292 }
Patrick Williamsb9dd7f82023-10-20 11:20:11 -05001293 });
Patrick Williams2af39222022-07-22 19:26:56 -05001294 sdbusplus::bus::match_t interfacesRemovedMatch(
1295 static_cast<sdbusplus::bus_t&>(*systemBus),
Brad Bishop10a8c5f2020-12-07 21:40:07 -05001296 sdbusplus::bus::match::rules::interfacesRemoved(),
Matt Spinler8d1ac3a2023-02-02 09:48:04 -06001297 [&](sdbusplus::message_t& msg) {
Patrick Williamsdf190612023-05-10 07:51:34 -05001298 if (irContainsProbeInterface(msg, probeInterfaces))
1299 {
1300 propertiesChangedCallback(systemConfiguration, objServer);
1301 }
Patrick Williamsb9dd7f82023-10-20 11:20:11 -05001302 });
Brad Bishopc76af0f2020-12-04 13:50:23 -05001303
Ed Tanous49a888c2023-03-06 13:44:51 -08001304 boost::asio::post(io, [&]() {
1305 propertiesChangedCallback(systemConfiguration, objServer);
1306 });
James Feist4131aea2018-03-09 09:47:30 -08001307
James Feistfd1264a2018-05-03 12:10:00 -07001308 entityIface->register_method("ReScan", [&]() {
James Feist4dc617b2020-05-01 09:54:47 -07001309 propertiesChangedCallback(systemConfiguration, objServer);
James Feist75fdeeb2018-02-20 14:26:16 -08001310 });
John Edward Broadbentd97c6312023-10-26 20:32:07 +00001311 tryIfaceInitialize(entityIface);
James Feist8f2710a2018-05-09 17:18:55 -07001312
James Feist1df06a42019-04-11 14:23:04 -07001313 if (fwVersionIsSame())
1314 {
1315 if (std::filesystem::is_regular_file(currentConfiguration))
1316 {
1317 // this file could just be deleted, but it's nice for debug
1318 std::filesystem::create_directory(tempConfigDir);
1319 std::filesystem::remove(lastConfiguration);
1320 std::filesystem::copy(currentConfiguration, lastConfiguration);
1321 std::filesystem::remove(currentConfiguration);
1322
1323 std::ifstream jsonStream(lastConfiguration);
1324 if (jsonStream.good())
1325 {
1326 auto data = nlohmann::json::parse(jsonStream, nullptr, false);
1327 if (data.is_discarded())
1328 {
1329 std::cerr << "syntax error in " << lastConfiguration
1330 << "\n";
1331 }
1332 else
1333 {
1334 lastJson = std::move(data);
1335 }
1336 }
1337 else
1338 {
1339 std::cerr << "unable to open " << lastConfiguration << "\n";
1340 }
1341 }
1342 }
1343 else
1344 {
1345 // not an error, just logging at this level to make it in the journal
1346 std::cerr << "Clearing previous configuration\n";
1347 std::filesystem::remove(currentConfiguration);
1348 }
1349
1350 // some boards only show up after power is on, we want to not say they are
1351 // removed until the same state happens
Ed Tanous07d467b2021-02-23 14:48:37 -08001352 setupPowerMatch(systemBus);
James Feist1df06a42019-04-11 14:23:04 -07001353
James Feist1b2e2242018-01-30 13:45:19 -08001354 io.run();
James Feist3cb5fec2018-01-23 14:41:51 -08001355
1356 return 0;
James Feist75fdeeb2018-02-20 14:26:16 -08001357}