blob: c8f55a6c45cf238871db94d82d40e4fd0d315889 [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
Alexander Hansen8feb0452025-09-15 14:29:20 +02006#include <phosphor-logging/lg2.hpp>
Christopher Meis12bea9b2025-04-03 10:14:42 +02007
Ed Tanousdbf95b22025-10-13 11:38:35 -07008#include <flat_map>
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
Christopher Meis12bea9b2025-04-03 10:14:42 +020014namespace dbus_interface
15{
16
17const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]");
18const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]");
19
Alexander Hansen89737252025-08-04 15:15:13 +020020EMDBusInterface::EMDBusInterface(boost::asio::io_context& io,
21 sdbusplus::asio::object_server& objServer) :
22 io(io), objServer(objServer)
23{}
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 {
Alexander Hansen8feb0452025-09-15 14:29:20 +020033 lg2::error(
34 "Unable to initialize dbus interface : {ERR} object Path : {PATH} interface name : {INTF}",
35 "ERR", e, "PATH", iface->get_object_path(), "INTF",
36 iface->get_interface_name());
Christopher Meis12bea9b2025-04-03 10:14:42 +020037 }
38}
39
Alexander Hansen57604ed2025-06-27 13:22:28 +020040std::shared_ptr<sdbusplus::asio::dbus_interface>
Alexander Hansen89737252025-08-04 15:15:13 +020041 EMDBusInterface::createInterface(const std::string& path,
42 const std::string& interface,
43 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
Alexander Hansen89737252025-08-04 15:15:13 +020065void EMDBusInterface::createDeleteObjectMethod(
Christopher Meis12bea9b2025-04-03 10:14:42 +020066 const std::string& jsonPointerPath,
67 const std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
Alexander Hansen89737252025-08-04 15:15:13 +020068 nlohmann::json& systemConfiguration)
Christopher Meis12bea9b2025-04-03 10:14:42 +020069{
70 std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface;
71 iface->register_method(
Alexander Hansen89737252025-08-04 15:15:13 +020072 "Delete", [this, &systemConfiguration, interface,
73 jsonPointerPath{std::string(jsonPointerPath)}]() {
Christopher Meis12bea9b2025-04-03 10:14:42 +020074 std::shared_ptr<sdbusplus::asio::dbus_interface> dbusInterface =
75 interface.lock();
76 if (!dbusInterface)
77 {
78 // this technically can't happen as the pointer is pointing to
79 // us
80 throw DBusInternalError();
81 }
82 nlohmann::json::json_pointer ptr(jsonPointerPath);
83 systemConfiguration[ptr] = nullptr;
84
85 // todo(james): dig through sdbusplus to find out why we can't
86 // delete it in a method call
Alexander Hansen89737252025-08-04 15:15:13 +020087 boost::asio::post(io, [dbusInterface, this]() mutable {
Christopher Meis12bea9b2025-04-03 10:14:42 +020088 objServer.remove_interface(dbusInterface);
89 });
90
Christopher Meisf7252572025-06-11 13:22:05 +020091 if (!writeJsonFiles(systemConfiguration))
Christopher Meis12bea9b2025-04-03 10:14:42 +020092 {
Alexander Hansen8feb0452025-09-15 14:29:20 +020093 lg2::error("error setting json file");
Christopher Meis12bea9b2025-04-03 10:14:42 +020094 throw DBusInternalError();
95 }
96 });
97}
98
Alexander Hansend72dc332025-06-10 10:49:24 +020099static bool checkArrayElementsSameType(nlohmann::json& value)
100{
101 nlohmann::json::array_t* arr = value.get_ptr<nlohmann::json::array_t*>();
102 if (arr == nullptr)
103 {
104 return false;
105 }
106
107 if (arr->empty())
108 {
109 return true;
110 }
111
112 nlohmann::json::value_t firstType = value[0].type();
113 return std::ranges::all_of(value, [firstType](const nlohmann::json& el) {
114 return el.type() == firstType;
115 });
116}
117
Alexander Hansen91e360e2025-06-10 12:21:30 +0200118static nlohmann::json::value_t getDBusType(
119 const nlohmann::json& value, nlohmann::json::value_t type,
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200120 sdbusplus::asio::PropertyPermission permission)
121{
122 const bool array = value.type() == nlohmann::json::value_t::array;
123
124 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
125 {
126 // all setable numbers are doubles as it is difficult to always
127 // create a configuration file with all whole numbers as decimals
128 // i.e. 1.0
129 if (array)
130 {
131 if (value[0].is_number())
132 {
Alexander Hansen91e360e2025-06-10 12:21:30 +0200133 return nlohmann::json::value_t::number_float;
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200134 }
135 }
136 else if (value.is_number())
137 {
Alexander Hansen91e360e2025-06-10 12:21:30 +0200138 return nlohmann::json::value_t::number_float;
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200139 }
140 }
141
Alexander Hansen91e360e2025-06-10 12:21:30 +0200142 return type;
143}
144
145static void populateInterfacePropertyFromJson(
146 nlohmann::json& systemConfiguration, const std::string& path,
147 const nlohmann::json& key, const nlohmann::json& value,
148 nlohmann::json::value_t type,
149 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
150 sdbusplus::asio::PropertyPermission permission)
151{
152 const auto modifiedType = getDBusType(value, type, permission);
153
154 switch (modifiedType)
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200155 {
156 case (nlohmann::json::value_t::boolean):
157 {
Alexander Hansen5531eea2025-08-22 11:03:09 +0200158 addValueToDBus<bool>(key, value, *iface, permission,
159 systemConfiguration, path);
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200160 break;
161 }
162 case (nlohmann::json::value_t::number_integer):
163 {
164 addValueToDBus<int64_t>(key, value, *iface, permission,
165 systemConfiguration, path);
166 break;
167 }
168 case (nlohmann::json::value_t::number_unsigned):
169 {
170 addValueToDBus<uint64_t>(key, value, *iface, permission,
171 systemConfiguration, path);
172 break;
173 }
174 case (nlohmann::json::value_t::number_float):
175 {
176 addValueToDBus<double>(key, value, *iface, permission,
177 systemConfiguration, path);
178 break;
179 }
180 case (nlohmann::json::value_t::string):
181 {
182 addValueToDBus<std::string>(key, value, *iface, permission,
183 systemConfiguration, path);
184 break;
185 }
186 default:
187 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200188 lg2::error(
189 "Unexpected json type in system configuration {KEY}: {VALUE}",
190 "KEY", key, "VALUE", value.type_name());
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200191 break;
192 }
193 }
194}
195
Christopher Meis12bea9b2025-04-03 10:14:42 +0200196// adds simple json types to interface's properties
Alexander Hansen89737252025-08-04 15:15:13 +0200197void EMDBusInterface::populateInterfaceFromJson(
198 nlohmann::json& systemConfiguration, const std::string& jsonPointerPath,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200199 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
Alexander Hansen89737252025-08-04 15:15:13 +0200200 nlohmann::json& dict, sdbusplus::asio::PropertyPermission permission)
Christopher Meis12bea9b2025-04-03 10:14:42 +0200201{
202 for (const auto& [key, value] : dict.items())
203 {
204 auto type = value.type();
Christopher Meis12bea9b2025-04-03 10:14:42 +0200205 if (value.type() == nlohmann::json::value_t::array)
206 {
Christopher Meis12bea9b2025-04-03 10:14:42 +0200207 if (value.empty())
208 {
209 continue;
210 }
211 type = value[0].type();
Alexander Hansend72dc332025-06-10 10:49:24 +0200212 if (!checkArrayElementsSameType(value))
Christopher Meis12bea9b2025-04-03 10:14:42 +0200213 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200214 lg2::error("dbus format error {VALUE}", "VALUE", value);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200215 continue;
216 }
217 }
218 if (type == nlohmann::json::value_t::object)
219 {
220 continue; // handled elsewhere
221 }
222
223 std::string path = jsonPointerPath;
224 path.append("/").append(key);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200225
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200226 populateInterfacePropertyFromJson(systemConfiguration, path, key, value,
227 type, iface, permission);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200228 }
229 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
230 {
Alexander Hansen89737252025-08-04 15:15:13 +0200231 createDeleteObjectMethod(jsonPointerPath, iface, systemConfiguration);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200232 }
233 tryIfaceInitialize(iface);
234}
235
Alexander Hansenf9a40242025-10-14 15:34:47 +0200236// @brief: throws on error
237static void addObjectRuntimeValidateJson(const nlohmann::json& newData,
238 const std::string* type)
239{
240 if constexpr (!ENABLE_RUNTIME_VALIDATE_JSON)
241 {
242 return;
243 }
244
245 const std::filesystem::path schemaPath =
246 std::filesystem::path(schemaDirectory) / "exposes_record.json";
247
248 std::ifstream schemaFile{schemaPath};
249
250 if (!schemaFile.good())
251 {
252 throw std::invalid_argument("No schema avaliable, cannot validate.");
253 }
254 nlohmann::json schema =
255 nlohmann::json::parse(schemaFile, nullptr, false, true);
256 if (schema.is_discarded())
257 {
258 lg2::error("Schema not legal: {TYPE}.json", "TYPE", *type);
259 throw DBusInternalError();
260 }
261
262 if (!validateJson(schema, newData))
263 {
264 throw std::invalid_argument("Data does not match schema");
265 }
266}
267
Alexander Hansena182cb72025-10-14 15:22:34 +0200268void EMDBusInterface::addObject(
269 const std::flat_map<std::string, JsonVariantType, std::less<>>& data,
270 nlohmann::json& systemConfiguration, const std::string& jsonPointerPath,
271 const std::string& path, const std::string& board)
272{
273 nlohmann::json::json_pointer ptr(jsonPointerPath);
274 nlohmann::json& base = systemConfiguration[ptr];
275 auto findExposes = base.find("Exposes");
276
277 if (findExposes == base.end())
278 {
279 throw std::invalid_argument("Entity must have children.");
280 }
281
282 // this will throw invalid-argument to sdbusplus if invalid json
283 nlohmann::json newData{};
284 for (const auto& item : data)
285 {
286 nlohmann::json& newJson = newData[item.first];
287 std::visit(
288 [&newJson](auto&& val) {
289 newJson = std::forward<decltype(val)>(val);
290 },
291 item.second);
292 }
293
294 auto findName = newData.find("Name");
295 auto findType = newData.find("Type");
296 if (findName == newData.end() || findType == newData.end())
297 {
298 throw std::invalid_argument("AddObject missing Name or Type");
299 }
300 const std::string* type = findType->get_ptr<const std::string*>();
301 const std::string* name = findName->get_ptr<const std::string*>();
302 if (type == nullptr || name == nullptr)
303 {
304 throw std::invalid_argument("Type and Name must be a string.");
305 }
306
307 bool foundNull = false;
308 size_t lastIndex = 0;
309 // we add in the "exposes"
310 for (const auto& expose : *findExposes)
311 {
312 if (expose.is_null())
313 {
314 foundNull = true;
315 continue;
316 }
317
318 if (expose["Name"] == *name && expose["Type"] == *type)
319 {
320 throw std::invalid_argument("Field already in JSON, not adding");
321 }
322
323 if (foundNull)
324 {
325 continue;
326 }
327
328 lastIndex++;
329 }
330
Alexander Hansenf9a40242025-10-14 15:34:47 +0200331 addObjectRuntimeValidateJson(newData, type);
Alexander Hansena182cb72025-10-14 15:22:34 +0200332
333 if (foundNull)
334 {
335 findExposes->at(lastIndex) = newData;
336 }
337 else
338 {
339 findExposes->push_back(newData);
340 }
341 if (!writeJsonFiles(systemConfiguration))
342 {
343 lg2::error("Error writing json files");
344 }
345 std::string dbusName = *name;
346
347 std::regex_replace(dbusName.begin(), dbusName.begin(), dbusName.end(),
348 illegalDbusMemberRegex, "_");
349
350 std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
351 createInterface(path + "/" + dbusName,
352 "xyz.openbmc_project.Configuration." + *type, board,
353 true);
354 // permission is read-write, as since we just created it, must be
355 // runtime modifiable
356 populateInterfaceFromJson(
357 systemConfiguration,
358 jsonPointerPath + "/Exposes/" + std::to_string(lastIndex), interface,
359 newData, sdbusplus::asio::PropertyPermission::readWrite);
360}
361
Alexander Hansen57604ed2025-06-27 13:22:28 +0200362void EMDBusInterface::createAddObjectMethod(
Alexander Hansen89737252025-08-04 15:15:13 +0200363 const std::string& jsonPointerPath, const std::string& path,
364 nlohmann::json& systemConfiguration, const std::string& board)
Christopher Meis12bea9b2025-04-03 10:14:42 +0200365{
Alexander Hansen89737252025-08-04 15:15:13 +0200366 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
367 createInterface(path, "xyz.openbmc_project.AddObject", board);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200368
369 iface->register_method(
370 "AddObject",
Alexander Hansen89737252025-08-04 15:15:13 +0200371 [&systemConfiguration, jsonPointerPath{std::string(jsonPointerPath)},
Alexander Hansena182cb72025-10-14 15:22:34 +0200372 path{std::string(path)}, board{std::string(board)},
Ed Tanousdbf95b22025-10-13 11:38:35 -0700373 this](const std::flat_map<std::string, JsonVariantType, std::less<>>&
Alexander Hansen57604ed2025-06-27 13:22:28 +0200374 data) {
Alexander Hansena182cb72025-10-14 15:22:34 +0200375 addObject(data, systemConfiguration, jsonPointerPath, path, board);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200376 });
377 tryIfaceInitialize(iface);
378}
379
380std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>&
Alexander Hansen57604ed2025-06-27 13:22:28 +0200381 EMDBusInterface::getDeviceInterfaces(const nlohmann::json& device)
Christopher Meis12bea9b2025-04-03 10:14:42 +0200382{
383 return inventory[device["Name"].get<std::string>()];
384}
385
386} // namespace dbus_interface