blob: cea5d42fc0a2e20335045e4d673b42cecd1e3be3 [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>
8
Christopher Meis59ef1e72025-04-16 08:53:25 +02009#include <fstream>
Christopher Meis12bea9b2025-04-03 10:14:42 +020010#include <regex>
11#include <string>
12#include <vector>
13
14using JsonVariantType =
15 std::variant<std::vector<std::string>, std::vector<double>, std::string,
16 int64_t, uint64_t, double, int32_t, uint32_t, int16_t,
17 uint16_t, uint8_t, bool>;
18
19namespace dbus_interface
20{
21
22const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]");
23const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]");
24
Christopher Meis12bea9b2025-04-03 10:14:42 +020025void tryIfaceInitialize(std::shared_ptr<sdbusplus::asio::dbus_interface>& iface)
26{
27 try
28 {
29 iface->initialize();
30 }
31 catch (std::exception& e)
32 {
33 std::cerr << "Unable to initialize dbus interface : " << e.what()
34 << "\n"
35 << "object Path : " << iface->get_object_path() << "\n"
36 << "interface name : " << iface->get_interface_name() << "\n";
37 }
38}
39
Alexander Hansen57604ed2025-06-27 13:22:28 +020040std::shared_ptr<sdbusplus::asio::dbus_interface>
41 EMDBusInterface::createInterface(
42 sdbusplus::asio::object_server& objServer, const std::string& path,
43 const std::string& interface, const std::string& parent, bool checkNull)
Christopher Meis12bea9b2025-04-03 10:14:42 +020044{
45 // on first add we have no reason to check for null before add, as there
46 // won't be any. For dynamically added interfaces, we check for null so that
47 // a constant delete/add will not create a memory leak
48
49 auto ptr = objServer.add_interface(path, interface);
50 auto& dataVector = inventory[parent];
51 if (checkNull)
52 {
53 auto it = std::find_if(dataVector.begin(), dataVector.end(),
54 [](const auto& p) { return p.expired(); });
55 if (it != dataVector.end())
56 {
57 *it = ptr;
58 return ptr;
59 }
60 }
61 dataVector.emplace_back(ptr);
62 return ptr;
63}
64
65void createDeleteObjectMethod(
66 const std::string& jsonPointerPath,
67 const std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
68 sdbusplus::asio::object_server& objServer,
Alexander Hansena555acf2025-06-27 11:59:10 +020069 nlohmann::json& systemConfiguration, boost::asio::io_context& io)
Christopher Meis12bea9b2025-04-03 10:14:42 +020070{
71 std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface;
72 iface->register_method(
73 "Delete", [&objServer, &systemConfiguration, interface,
Alexander Hansena555acf2025-06-27 11:59:10 +020074 jsonPointerPath{std::string(jsonPointerPath)}, &io]() {
Christopher Meis12bea9b2025-04-03 10:14:42 +020075 std::shared_ptr<sdbusplus::asio::dbus_interface> dbusInterface =
76 interface.lock();
77 if (!dbusInterface)
78 {
79 // this technically can't happen as the pointer is pointing to
80 // us
81 throw DBusInternalError();
82 }
83 nlohmann::json::json_pointer ptr(jsonPointerPath);
84 systemConfiguration[ptr] = nullptr;
85
86 // todo(james): dig through sdbusplus to find out why we can't
87 // delete it in a method call
88 boost::asio::post(io, [&objServer, dbusInterface]() mutable {
89 objServer.remove_interface(dbusInterface);
90 });
91
Christopher Meisf7252572025-06-11 13:22:05 +020092 if (!writeJsonFiles(systemConfiguration))
Christopher Meis12bea9b2025-04-03 10:14:42 +020093 {
94 std::cerr << "error setting json file\n";
95 throw DBusInternalError();
96 }
97 });
98}
99
Alexander Hansend72dc332025-06-10 10:49:24 +0200100static bool checkArrayElementsSameType(nlohmann::json& value)
101{
102 nlohmann::json::array_t* arr = value.get_ptr<nlohmann::json::array_t*>();
103 if (arr == nullptr)
104 {
105 return false;
106 }
107
108 if (arr->empty())
109 {
110 return true;
111 }
112
113 nlohmann::json::value_t firstType = value[0].type();
114 return std::ranges::all_of(value, [firstType](const nlohmann::json& el) {
115 return el.type() == firstType;
116 });
117}
118
Alexander Hansen91e360e2025-06-10 12:21:30 +0200119static nlohmann::json::value_t getDBusType(
120 const nlohmann::json& value, nlohmann::json::value_t type,
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200121 sdbusplus::asio::PropertyPermission permission)
122{
123 const bool array = value.type() == nlohmann::json::value_t::array;
124
125 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
126 {
127 // all setable numbers are doubles as it is difficult to always
128 // create a configuration file with all whole numbers as decimals
129 // i.e. 1.0
130 if (array)
131 {
132 if (value[0].is_number())
133 {
Alexander Hansen91e360e2025-06-10 12:21:30 +0200134 return nlohmann::json::value_t::number_float;
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200135 }
136 }
137 else if (value.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
Alexander Hansen91e360e2025-06-10 12:21:30 +0200143 return type;
144}
145
146static void populateInterfacePropertyFromJson(
147 nlohmann::json& systemConfiguration, const std::string& path,
148 const nlohmann::json& key, const nlohmann::json& value,
149 nlohmann::json::value_t type,
150 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
151 sdbusplus::asio::PropertyPermission permission)
152{
153 const auto modifiedType = getDBusType(value, type, permission);
154
155 switch (modifiedType)
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200156 {
157 case (nlohmann::json::value_t::boolean):
158 {
Alexander Hansen0123f8a2025-06-27 12:11:08 +0200159 // todo: array of bool isn't detected correctly by
160 // sdbusplus, change it to numbers
161 addValueToDBus<uint64_t, bool>(key, value, *iface, permission,
162 systemConfiguration, path);
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200163 break;
164 }
165 case (nlohmann::json::value_t::number_integer):
166 {
167 addValueToDBus<int64_t>(key, value, *iface, permission,
168 systemConfiguration, path);
169 break;
170 }
171 case (nlohmann::json::value_t::number_unsigned):
172 {
173 addValueToDBus<uint64_t>(key, value, *iface, permission,
174 systemConfiguration, path);
175 break;
176 }
177 case (nlohmann::json::value_t::number_float):
178 {
179 addValueToDBus<double>(key, value, *iface, permission,
180 systemConfiguration, path);
181 break;
182 }
183 case (nlohmann::json::value_t::string):
184 {
185 addValueToDBus<std::string>(key, value, *iface, permission,
186 systemConfiguration, path);
187 break;
188 }
189 default:
190 {
191 std::cerr << "Unexpected json type in system configuration " << key
192 << ": " << value.type_name() << "\n";
193 break;
194 }
195 }
196}
197
Christopher Meis12bea9b2025-04-03 10:14:42 +0200198// adds simple json types to interface's properties
199void populateInterfaceFromJson(
Alexander Hansena555acf2025-06-27 11:59:10 +0200200 boost::asio::io_context& io, nlohmann::json& systemConfiguration,
201 const std::string& jsonPointerPath,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200202 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
203 nlohmann::json& dict, sdbusplus::asio::object_server& objServer,
204 sdbusplus::asio::PropertyPermission permission)
205{
206 for (const auto& [key, value] : dict.items())
207 {
208 auto type = value.type();
Christopher Meis12bea9b2025-04-03 10:14:42 +0200209 if (value.type() == nlohmann::json::value_t::array)
210 {
Christopher Meis12bea9b2025-04-03 10:14:42 +0200211 if (value.empty())
212 {
213 continue;
214 }
215 type = value[0].type();
Alexander Hansend72dc332025-06-10 10:49:24 +0200216 if (!checkArrayElementsSameType(value))
Christopher Meis12bea9b2025-04-03 10:14:42 +0200217 {
218 std::cerr << "dbus format error" << value << "\n";
219 continue;
220 }
221 }
222 if (type == nlohmann::json::value_t::object)
223 {
224 continue; // handled elsewhere
225 }
226
227 std::string path = jsonPointerPath;
228 path.append("/").append(key);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200229
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200230 populateInterfacePropertyFromJson(systemConfiguration, path, key, value,
231 type, iface, permission);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200232 }
233 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
234 {
235 createDeleteObjectMethod(jsonPointerPath, iface, objServer,
Alexander Hansena555acf2025-06-27 11:59:10 +0200236 systemConfiguration, io);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200237 }
238 tryIfaceInitialize(iface);
239}
240
Alexander Hansen57604ed2025-06-27 13:22:28 +0200241void EMDBusInterface::createAddObjectMethod(
Alexander Hansena555acf2025-06-27 11:59:10 +0200242 boost::asio::io_context& io, const std::string& jsonPointerPath,
243 const std::string& path, nlohmann::json& systemConfiguration,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200244 sdbusplus::asio::object_server& objServer, const std::string& board)
245{
246 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = createInterface(
247 objServer, path, "xyz.openbmc_project.AddObject", board);
248
249 iface->register_method(
250 "AddObject",
251 [&systemConfiguration, &objServer,
252 jsonPointerPath{std::string(jsonPointerPath)}, path{std::string(path)},
Alexander Hansen57604ed2025-06-27 13:22:28 +0200253 board, &io,
254 this](const boost::container::flat_map<std::string, JsonVariantType>&
255 data) {
Christopher Meis12bea9b2025-04-03 10:14:42 +0200256 nlohmann::json::json_pointer ptr(jsonPointerPath);
257 nlohmann::json& base = systemConfiguration[ptr];
258 auto findExposes = base.find("Exposes");
259
260 if (findExposes == base.end())
261 {
262 throw std::invalid_argument("Entity must have children.");
263 }
264
265 // this will throw invalid-argument to sdbusplus if invalid json
266 nlohmann::json newData{};
267 for (const auto& item : data)
268 {
269 nlohmann::json& newJson = newData[item.first];
270 std::visit(
271 [&newJson](auto&& val) {
272 newJson = std::forward<decltype(val)>(val);
273 },
274 item.second);
275 }
276
277 auto findName = newData.find("Name");
278 auto findType = newData.find("Type");
279 if (findName == newData.end() || findType == newData.end())
280 {
281 throw std::invalid_argument("AddObject missing Name or Type");
282 }
283 const std::string* type = findType->get_ptr<const std::string*>();
284 const std::string* name = findName->get_ptr<const std::string*>();
285 if (type == nullptr || name == nullptr)
286 {
287 throw std::invalid_argument("Type and Name must be a string.");
288 }
289
290 bool foundNull = false;
291 size_t lastIndex = 0;
292 // we add in the "exposes"
293 for (const auto& expose : *findExposes)
294 {
295 if (expose.is_null())
296 {
297 foundNull = true;
298 continue;
299 }
300
301 if (expose["Name"] == *name && expose["Type"] == *type)
302 {
303 throw std::invalid_argument(
304 "Field already in JSON, not adding");
305 }
306
307 if (foundNull)
308 {
309 continue;
310 }
311
312 lastIndex++;
313 }
314
Christopher Meisf7252572025-06-11 13:22:05 +0200315 std::ifstream schemaFile(std::string(schemaDirectory) + "/" +
316 boost::to_lower_copy(*type) + ".json");
Christopher Meis12bea9b2025-04-03 10:14:42 +0200317 // todo(james) we might want to also make a list of 'can add'
318 // interfaces but for now I think the assumption if there is a
319 // schema avaliable that it is allowed to update is fine
320 if (!schemaFile.good())
321 {
322 throw std::invalid_argument(
323 "No schema avaliable, cannot validate.");
324 }
325 nlohmann::json schema =
326 nlohmann::json::parse(schemaFile, nullptr, false, true);
327 if (schema.is_discarded())
328 {
329 std::cerr << "Schema not legal" << *type << ".json\n";
330 throw DBusInternalError();
331 }
Christopher Meisf7252572025-06-11 13:22:05 +0200332 if (!validateJson(schema, newData))
Christopher Meis12bea9b2025-04-03 10:14:42 +0200333 {
334 throw std::invalid_argument("Data does not match schema");
335 }
336 if (foundNull)
337 {
338 findExposes->at(lastIndex) = newData;
339 }
340 else
341 {
342 findExposes->push_back(newData);
343 }
Christopher Meisf7252572025-06-11 13:22:05 +0200344 if (!writeJsonFiles(systemConfiguration))
Christopher Meis12bea9b2025-04-03 10:14:42 +0200345 {
346 std::cerr << "Error writing json files\n";
347 throw DBusInternalError();
348 }
349 std::string dbusName = *name;
350
351 std::regex_replace(dbusName.begin(), dbusName.begin(),
352 dbusName.end(), illegalDbusMemberRegex, "_");
353
354 std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
355 createInterface(objServer, path + "/" + dbusName,
356 "xyz.openbmc_project.Configuration." + *type,
357 board, true);
358 // permission is read-write, as since we just created it, must be
359 // runtime modifiable
360 populateInterfaceFromJson(
Alexander Hansena555acf2025-06-27 11:59:10 +0200361 io, systemConfiguration,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200362 jsonPointerPath + "/Exposes/" + std::to_string(lastIndex),
363 interface, newData, objServer,
364 sdbusplus::asio::PropertyPermission::readWrite);
365 });
366 tryIfaceInitialize(iface);
367}
368
369std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>&
Alexander Hansen57604ed2025-06-27 13:22:28 +0200370 EMDBusInterface::getDeviceInterfaces(const nlohmann::json& device)
Christopher Meis12bea9b2025-04-03 10:14:42 +0200371{
372 return inventory[device["Name"].get<std::string>()];
373}
374
375} // namespace dbus_interface