blob: 461b27c2dd58ea3b31910841228e5388f18d5e8d [file] [log] [blame]
Christopher Meis12bea9b2025-04-03 10:14:42 +02001#include "dbus_interface.hpp"
2
3#include "perform_probe.hpp"
Christopher Meis59ef1e72025-04-16 08:53:25 +02004#include "utils.hpp"
Christopher Meis12bea9b2025-04-03 10:14:42 +02005
6#include <boost/algorithm/string/case_conv.hpp>
7#include <boost/container/flat_map.hpp>
Alexander Hansen8feb0452025-09-15 14:29:20 +02008#include <phosphor-logging/lg2.hpp>
Christopher Meis12bea9b2025-04-03 10:14:42 +02009
Christopher Meis59ef1e72025-04-16 08:53:25 +020010#include <fstream>
Christopher Meis12bea9b2025-04-03 10:14:42 +020011#include <regex>
12#include <string>
13#include <vector>
14
15using JsonVariantType =
16 std::variant<std::vector<std::string>, std::vector<double>, std::string,
17 int64_t, uint64_t, double, int32_t, uint32_t, int16_t,
18 uint16_t, uint8_t, bool>;
19
20namespace dbus_interface
21{
22
23const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]");
24const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]");
25
Alexander Hansen89737252025-08-04 15:15:13 +020026EMDBusInterface::EMDBusInterface(boost::asio::io_context& io,
27 sdbusplus::asio::object_server& objServer) :
28 io(io), objServer(objServer)
29{}
30
Christopher Meis12bea9b2025-04-03 10:14:42 +020031void tryIfaceInitialize(std::shared_ptr<sdbusplus::asio::dbus_interface>& iface)
32{
33 try
34 {
35 iface->initialize();
36 }
37 catch (std::exception& e)
38 {
Alexander Hansen8feb0452025-09-15 14:29:20 +020039 lg2::error(
40 "Unable to initialize dbus interface : {ERR} object Path : {PATH} interface name : {INTF}",
41 "ERR", e, "PATH", iface->get_object_path(), "INTF",
42 iface->get_interface_name());
Christopher Meis12bea9b2025-04-03 10:14:42 +020043 }
44}
45
Alexander Hansen57604ed2025-06-27 13:22:28 +020046std::shared_ptr<sdbusplus::asio::dbus_interface>
Alexander Hansen89737252025-08-04 15:15:13 +020047 EMDBusInterface::createInterface(const std::string& path,
48 const std::string& interface,
49 const std::string& parent, bool checkNull)
Christopher Meis12bea9b2025-04-03 10:14:42 +020050{
51 // on first add we have no reason to check for null before add, as there
52 // won't be any. For dynamically added interfaces, we check for null so that
53 // a constant delete/add will not create a memory leak
54
55 auto ptr = objServer.add_interface(path, interface);
56 auto& dataVector = inventory[parent];
57 if (checkNull)
58 {
59 auto it = std::find_if(dataVector.begin(), dataVector.end(),
60 [](const auto& p) { return p.expired(); });
61 if (it != dataVector.end())
62 {
63 *it = ptr;
64 return ptr;
65 }
66 }
67 dataVector.emplace_back(ptr);
68 return ptr;
69}
70
Alexander Hansen89737252025-08-04 15:15:13 +020071void EMDBusInterface::createDeleteObjectMethod(
Christopher Meis12bea9b2025-04-03 10:14:42 +020072 const std::string& jsonPointerPath,
73 const std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
Alexander Hansen89737252025-08-04 15:15:13 +020074 nlohmann::json& systemConfiguration)
Christopher Meis12bea9b2025-04-03 10:14:42 +020075{
76 std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface;
77 iface->register_method(
Alexander Hansen89737252025-08-04 15:15:13 +020078 "Delete", [this, &systemConfiguration, interface,
79 jsonPointerPath{std::string(jsonPointerPath)}]() {
Christopher Meis12bea9b2025-04-03 10:14:42 +020080 std::shared_ptr<sdbusplus::asio::dbus_interface> dbusInterface =
81 interface.lock();
82 if (!dbusInterface)
83 {
84 // this technically can't happen as the pointer is pointing to
85 // us
86 throw DBusInternalError();
87 }
88 nlohmann::json::json_pointer ptr(jsonPointerPath);
89 systemConfiguration[ptr] = nullptr;
90
91 // todo(james): dig through sdbusplus to find out why we can't
92 // delete it in a method call
Alexander Hansen89737252025-08-04 15:15:13 +020093 boost::asio::post(io, [dbusInterface, this]() mutable {
Christopher Meis12bea9b2025-04-03 10:14:42 +020094 objServer.remove_interface(dbusInterface);
95 });
96
Christopher Meisf7252572025-06-11 13:22:05 +020097 if (!writeJsonFiles(systemConfiguration))
Christopher Meis12bea9b2025-04-03 10:14:42 +020098 {
Alexander Hansen8feb0452025-09-15 14:29:20 +020099 lg2::error("error setting json file");
Christopher Meis12bea9b2025-04-03 10:14:42 +0200100 throw DBusInternalError();
101 }
102 });
103}
104
Alexander Hansend72dc332025-06-10 10:49:24 +0200105static bool checkArrayElementsSameType(nlohmann::json& value)
106{
107 nlohmann::json::array_t* arr = value.get_ptr<nlohmann::json::array_t*>();
108 if (arr == nullptr)
109 {
110 return false;
111 }
112
113 if (arr->empty())
114 {
115 return true;
116 }
117
118 nlohmann::json::value_t firstType = value[0].type();
119 return std::ranges::all_of(value, [firstType](const nlohmann::json& el) {
120 return el.type() == firstType;
121 });
122}
123
Alexander Hansen91e360e2025-06-10 12:21:30 +0200124static nlohmann::json::value_t getDBusType(
125 const nlohmann::json& value, nlohmann::json::value_t type,
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200126 sdbusplus::asio::PropertyPermission permission)
127{
128 const bool array = value.type() == nlohmann::json::value_t::array;
129
130 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
131 {
132 // all setable numbers are doubles as it is difficult to always
133 // create a configuration file with all whole numbers as decimals
134 // i.e. 1.0
135 if (array)
136 {
137 if (value[0].is_number())
138 {
Alexander Hansen91e360e2025-06-10 12:21:30 +0200139 return nlohmann::json::value_t::number_float;
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200140 }
141 }
142 else if (value.is_number())
143 {
Alexander Hansen91e360e2025-06-10 12:21:30 +0200144 return nlohmann::json::value_t::number_float;
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200145 }
146 }
147
Alexander Hansen91e360e2025-06-10 12:21:30 +0200148 return type;
149}
150
151static void populateInterfacePropertyFromJson(
152 nlohmann::json& systemConfiguration, const std::string& path,
153 const nlohmann::json& key, const nlohmann::json& value,
154 nlohmann::json::value_t type,
155 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
156 sdbusplus::asio::PropertyPermission permission)
157{
158 const auto modifiedType = getDBusType(value, type, permission);
159
160 switch (modifiedType)
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200161 {
162 case (nlohmann::json::value_t::boolean):
163 {
Alexander Hansen5531eea2025-08-22 11:03:09 +0200164 addValueToDBus<bool>(key, value, *iface, permission,
165 systemConfiguration, path);
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200166 break;
167 }
168 case (nlohmann::json::value_t::number_integer):
169 {
170 addValueToDBus<int64_t>(key, value, *iface, permission,
171 systemConfiguration, path);
172 break;
173 }
174 case (nlohmann::json::value_t::number_unsigned):
175 {
176 addValueToDBus<uint64_t>(key, value, *iface, permission,
177 systemConfiguration, path);
178 break;
179 }
180 case (nlohmann::json::value_t::number_float):
181 {
182 addValueToDBus<double>(key, value, *iface, permission,
183 systemConfiguration, path);
184 break;
185 }
186 case (nlohmann::json::value_t::string):
187 {
188 addValueToDBus<std::string>(key, value, *iface, permission,
189 systemConfiguration, path);
190 break;
191 }
192 default:
193 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200194 lg2::error(
195 "Unexpected json type in system configuration {KEY}: {VALUE}",
196 "KEY", key, "VALUE", value.type_name());
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200197 break;
198 }
199 }
200}
201
Christopher Meis12bea9b2025-04-03 10:14:42 +0200202// adds simple json types to interface's properties
Alexander Hansen89737252025-08-04 15:15:13 +0200203void EMDBusInterface::populateInterfaceFromJson(
204 nlohmann::json& systemConfiguration, const std::string& jsonPointerPath,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200205 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
Alexander Hansen89737252025-08-04 15:15:13 +0200206 nlohmann::json& dict, sdbusplus::asio::PropertyPermission permission)
Christopher Meis12bea9b2025-04-03 10:14:42 +0200207{
208 for (const auto& [key, value] : dict.items())
209 {
210 auto type = value.type();
Christopher Meis12bea9b2025-04-03 10:14:42 +0200211 if (value.type() == nlohmann::json::value_t::array)
212 {
Christopher Meis12bea9b2025-04-03 10:14:42 +0200213 if (value.empty())
214 {
215 continue;
216 }
217 type = value[0].type();
Alexander Hansend72dc332025-06-10 10:49:24 +0200218 if (!checkArrayElementsSameType(value))
Christopher Meis12bea9b2025-04-03 10:14:42 +0200219 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200220 lg2::error("dbus format error {VALUE}", "VALUE", value);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200221 continue;
222 }
223 }
224 if (type == nlohmann::json::value_t::object)
225 {
226 continue; // handled elsewhere
227 }
228
229 std::string path = jsonPointerPath;
230 path.append("/").append(key);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200231
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200232 populateInterfacePropertyFromJson(systemConfiguration, path, key, value,
233 type, iface, permission);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200234 }
235 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
236 {
Alexander Hansen89737252025-08-04 15:15:13 +0200237 createDeleteObjectMethod(jsonPointerPath, iface, systemConfiguration);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200238 }
239 tryIfaceInitialize(iface);
240}
241
Alexander Hansen57604ed2025-06-27 13:22:28 +0200242void EMDBusInterface::createAddObjectMethod(
Alexander Hansen89737252025-08-04 15:15:13 +0200243 const std::string& jsonPointerPath, const std::string& path,
244 nlohmann::json& systemConfiguration, const std::string& board)
Christopher Meis12bea9b2025-04-03 10:14:42 +0200245{
Alexander Hansen89737252025-08-04 15:15:13 +0200246 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
247 createInterface(path, "xyz.openbmc_project.AddObject", board);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200248
249 iface->register_method(
250 "AddObject",
Alexander Hansen89737252025-08-04 15:15:13 +0200251 [&systemConfiguration, jsonPointerPath{std::string(jsonPointerPath)},
252 path{std::string(path)}, board,
Alexander Hansen57604ed2025-06-27 13:22:28 +0200253 this](const boost::container::flat_map<std::string, JsonVariantType>&
254 data) {
Christopher Meis12bea9b2025-04-03 10:14:42 +0200255 nlohmann::json::json_pointer ptr(jsonPointerPath);
256 nlohmann::json& base = systemConfiguration[ptr];
257 auto findExposes = base.find("Exposes");
258
259 if (findExposes == base.end())
260 {
261 throw std::invalid_argument("Entity must have children.");
262 }
263
264 // this will throw invalid-argument to sdbusplus if invalid json
265 nlohmann::json newData{};
266 for (const auto& item : data)
267 {
268 nlohmann::json& newJson = newData[item.first];
269 std::visit(
270 [&newJson](auto&& val) {
271 newJson = std::forward<decltype(val)>(val);
272 },
273 item.second);
274 }
275
276 auto findName = newData.find("Name");
277 auto findType = newData.find("Type");
278 if (findName == newData.end() || findType == newData.end())
279 {
280 throw std::invalid_argument("AddObject missing Name or Type");
281 }
282 const std::string* type = findType->get_ptr<const std::string*>();
283 const std::string* name = findName->get_ptr<const std::string*>();
284 if (type == nullptr || name == nullptr)
285 {
286 throw std::invalid_argument("Type and Name must be a string.");
287 }
288
289 bool foundNull = false;
290 size_t lastIndex = 0;
291 // we add in the "exposes"
292 for (const auto& expose : *findExposes)
293 {
294 if (expose.is_null())
295 {
296 foundNull = true;
297 continue;
298 }
299
300 if (expose["Name"] == *name && expose["Type"] == *type)
301 {
302 throw std::invalid_argument(
303 "Field already in JSON, not adding");
304 }
305
306 if (foundNull)
307 {
308 continue;
309 }
310
311 lastIndex++;
312 }
313
Marc Olberdingd10dd462025-08-22 11:58:46 -0700314 if constexpr (ENABLE_RUNTIME_VALIDATE_JSON)
Christopher Meis12bea9b2025-04-03 10:14:42 +0200315 {
Marc Olberdingd10dd462025-08-22 11:58:46 -0700316 const std::filesystem::path schemaPath =
317 std::filesystem::path(schemaDirectory) /
318 "exposes_record.json";
319
320 std::ifstream schemaFile{schemaPath};
321
322 if (!schemaFile.good())
323 {
324 throw std::invalid_argument(
325 "No schema avaliable, cannot validate.");
326 }
327 nlohmann::json schema =
328 nlohmann::json::parse(schemaFile, nullptr, false, true);
329 if (schema.is_discarded())
330 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200331 lg2::error("Schema not legal: {TYPE}.json", "TYPE", *type);
Marc Olberdingd10dd462025-08-22 11:58:46 -0700332 throw DBusInternalError();
333 }
334
335 if (!validateJson(schema, newData))
336 {
337 throw std::invalid_argument("Data does not match schema");
338 }
Christopher Meis12bea9b2025-04-03 10:14:42 +0200339 }
Marc Olberdingd10dd462025-08-22 11:58:46 -0700340
Christopher Meis12bea9b2025-04-03 10:14:42 +0200341 if (foundNull)
342 {
343 findExposes->at(lastIndex) = newData;
344 }
345 else
346 {
347 findExposes->push_back(newData);
348 }
Christopher Meisf7252572025-06-11 13:22:05 +0200349 if (!writeJsonFiles(systemConfiguration))
Christopher Meis12bea9b2025-04-03 10:14:42 +0200350 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200351 lg2::error("Error writing json files");
Christopher Meis12bea9b2025-04-03 10:14:42 +0200352 }
353 std::string dbusName = *name;
354
355 std::regex_replace(dbusName.begin(), dbusName.begin(),
356 dbusName.end(), illegalDbusMemberRegex, "_");
357
358 std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
Alexander Hansen89737252025-08-04 15:15:13 +0200359 createInterface(path + "/" + dbusName,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200360 "xyz.openbmc_project.Configuration." + *type,
361 board, true);
362 // permission is read-write, as since we just created it, must be
363 // runtime modifiable
364 populateInterfaceFromJson(
Alexander Hansen89737252025-08-04 15:15:13 +0200365 systemConfiguration,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200366 jsonPointerPath + "/Exposes/" + std::to_string(lastIndex),
Alexander Hansen89737252025-08-04 15:15:13 +0200367 interface, newData,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200368 sdbusplus::asio::PropertyPermission::readWrite);
369 });
370 tryIfaceInitialize(iface);
371}
372
373std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>&
Alexander Hansen57604ed2025-06-27 13:22:28 +0200374 EMDBusInterface::getDeviceInterfaces(const nlohmann::json& device)
Christopher Meis12bea9b2025-04-03 10:14:42 +0200375{
376 return inventory[device["Name"].get<std::string>()];
377}
378
379} // namespace dbus_interface