blob: d7169ce12293c18e5705e0a9462678a1b87a55f0 [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
Christopher Meis12bea9b2025-04-03 10:14:42 +02006#include <boost/container/flat_map.hpp>
Alexander Hansen8feb0452025-09-15 14:29:20 +02007#include <phosphor-logging/lg2.hpp>
Christopher Meis12bea9b2025-04-03 10:14:42 +02008
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
Alexander Hansen89737252025-08-04 15:15:13 +020025EMDBusInterface::EMDBusInterface(boost::asio::io_context& io,
26 sdbusplus::asio::object_server& objServer) :
27 io(io), objServer(objServer)
28{}
29
Christopher Meis12bea9b2025-04-03 10:14:42 +020030void tryIfaceInitialize(std::shared_ptr<sdbusplus::asio::dbus_interface>& iface)
31{
32 try
33 {
34 iface->initialize();
35 }
36 catch (std::exception& e)
37 {
Alexander Hansen8feb0452025-09-15 14:29:20 +020038 lg2::error(
39 "Unable to initialize dbus interface : {ERR} object Path : {PATH} interface name : {INTF}",
40 "ERR", e, "PATH", iface->get_object_path(), "INTF",
41 iface->get_interface_name());
Christopher Meis12bea9b2025-04-03 10:14:42 +020042 }
43}
44
Alexander Hansen57604ed2025-06-27 13:22:28 +020045std::shared_ptr<sdbusplus::asio::dbus_interface>
Alexander Hansen89737252025-08-04 15:15:13 +020046 EMDBusInterface::createInterface(const std::string& path,
47 const std::string& interface,
48 const std::string& parent, bool checkNull)
Christopher Meis12bea9b2025-04-03 10:14:42 +020049{
50 // on first add we have no reason to check for null before add, as there
51 // won't be any. For dynamically added interfaces, we check for null so that
52 // a constant delete/add will not create a memory leak
53
54 auto ptr = objServer.add_interface(path, interface);
55 auto& dataVector = inventory[parent];
56 if (checkNull)
57 {
58 auto it = std::find_if(dataVector.begin(), dataVector.end(),
59 [](const auto& p) { return p.expired(); });
60 if (it != dataVector.end())
61 {
62 *it = ptr;
63 return ptr;
64 }
65 }
66 dataVector.emplace_back(ptr);
67 return ptr;
68}
69
Alexander Hansen89737252025-08-04 15:15:13 +020070void EMDBusInterface::createDeleteObjectMethod(
Christopher Meis12bea9b2025-04-03 10:14:42 +020071 const std::string& jsonPointerPath,
72 const std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
Alexander Hansen89737252025-08-04 15:15:13 +020073 nlohmann::json& systemConfiguration)
Christopher Meis12bea9b2025-04-03 10:14:42 +020074{
75 std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface;
76 iface->register_method(
Alexander Hansen89737252025-08-04 15:15:13 +020077 "Delete", [this, &systemConfiguration, interface,
78 jsonPointerPath{std::string(jsonPointerPath)}]() {
Christopher Meis12bea9b2025-04-03 10:14:42 +020079 std::shared_ptr<sdbusplus::asio::dbus_interface> dbusInterface =
80 interface.lock();
81 if (!dbusInterface)
82 {
83 // this technically can't happen as the pointer is pointing to
84 // us
85 throw DBusInternalError();
86 }
87 nlohmann::json::json_pointer ptr(jsonPointerPath);
88 systemConfiguration[ptr] = nullptr;
89
90 // todo(james): dig through sdbusplus to find out why we can't
91 // delete it in a method call
Alexander Hansen89737252025-08-04 15:15:13 +020092 boost::asio::post(io, [dbusInterface, this]() mutable {
Christopher Meis12bea9b2025-04-03 10:14:42 +020093 objServer.remove_interface(dbusInterface);
94 });
95
Christopher Meisf7252572025-06-11 13:22:05 +020096 if (!writeJsonFiles(systemConfiguration))
Christopher Meis12bea9b2025-04-03 10:14:42 +020097 {
Alexander Hansen8feb0452025-09-15 14:29:20 +020098 lg2::error("error setting json file");
Christopher Meis12bea9b2025-04-03 10:14:42 +020099 throw DBusInternalError();
100 }
101 });
102}
103
Alexander Hansend72dc332025-06-10 10:49:24 +0200104static bool checkArrayElementsSameType(nlohmann::json& value)
105{
106 nlohmann::json::array_t* arr = value.get_ptr<nlohmann::json::array_t*>();
107 if (arr == nullptr)
108 {
109 return false;
110 }
111
112 if (arr->empty())
113 {
114 return true;
115 }
116
117 nlohmann::json::value_t firstType = value[0].type();
118 return std::ranges::all_of(value, [firstType](const nlohmann::json& el) {
119 return el.type() == firstType;
120 });
121}
122
Alexander Hansen91e360e2025-06-10 12:21:30 +0200123static nlohmann::json::value_t getDBusType(
124 const nlohmann::json& value, nlohmann::json::value_t type,
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200125 sdbusplus::asio::PropertyPermission permission)
126{
127 const bool array = value.type() == nlohmann::json::value_t::array;
128
129 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
130 {
131 // all setable numbers are doubles as it is difficult to always
132 // create a configuration file with all whole numbers as decimals
133 // i.e. 1.0
134 if (array)
135 {
136 if (value[0].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 else if (value.is_number())
142 {
Alexander Hansen91e360e2025-06-10 12:21:30 +0200143 return nlohmann::json::value_t::number_float;
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200144 }
145 }
146
Alexander Hansen91e360e2025-06-10 12:21:30 +0200147 return type;
148}
149
150static void populateInterfacePropertyFromJson(
151 nlohmann::json& systemConfiguration, const std::string& path,
152 const nlohmann::json& key, const nlohmann::json& value,
153 nlohmann::json::value_t type,
154 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
155 sdbusplus::asio::PropertyPermission permission)
156{
157 const auto modifiedType = getDBusType(value, type, permission);
158
159 switch (modifiedType)
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200160 {
161 case (nlohmann::json::value_t::boolean):
162 {
Alexander Hansen5531eea2025-08-22 11:03:09 +0200163 addValueToDBus<bool>(key, value, *iface, permission,
164 systemConfiguration, path);
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200165 break;
166 }
167 case (nlohmann::json::value_t::number_integer):
168 {
169 addValueToDBus<int64_t>(key, value, *iface, permission,
170 systemConfiguration, path);
171 break;
172 }
173 case (nlohmann::json::value_t::number_unsigned):
174 {
175 addValueToDBus<uint64_t>(key, value, *iface, permission,
176 systemConfiguration, path);
177 break;
178 }
179 case (nlohmann::json::value_t::number_float):
180 {
181 addValueToDBus<double>(key, value, *iface, permission,
182 systemConfiguration, path);
183 break;
184 }
185 case (nlohmann::json::value_t::string):
186 {
187 addValueToDBus<std::string>(key, value, *iface, permission,
188 systemConfiguration, path);
189 break;
190 }
191 default:
192 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200193 lg2::error(
194 "Unexpected json type in system configuration {KEY}: {VALUE}",
195 "KEY", key, "VALUE", value.type_name());
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200196 break;
197 }
198 }
199}
200
Christopher Meis12bea9b2025-04-03 10:14:42 +0200201// adds simple json types to interface's properties
Alexander Hansen89737252025-08-04 15:15:13 +0200202void EMDBusInterface::populateInterfaceFromJson(
203 nlohmann::json& systemConfiguration, const std::string& jsonPointerPath,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200204 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface,
Alexander Hansen89737252025-08-04 15:15:13 +0200205 nlohmann::json& dict, sdbusplus::asio::PropertyPermission permission)
Christopher Meis12bea9b2025-04-03 10:14:42 +0200206{
207 for (const auto& [key, value] : dict.items())
208 {
209 auto type = value.type();
Christopher Meis12bea9b2025-04-03 10:14:42 +0200210 if (value.type() == nlohmann::json::value_t::array)
211 {
Christopher Meis12bea9b2025-04-03 10:14:42 +0200212 if (value.empty())
213 {
214 continue;
215 }
216 type = value[0].type();
Alexander Hansend72dc332025-06-10 10:49:24 +0200217 if (!checkArrayElementsSameType(value))
Christopher Meis12bea9b2025-04-03 10:14:42 +0200218 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200219 lg2::error("dbus format error {VALUE}", "VALUE", value);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200220 continue;
221 }
222 }
223 if (type == nlohmann::json::value_t::object)
224 {
225 continue; // handled elsewhere
226 }
227
228 std::string path = jsonPointerPath;
229 path.append("/").append(key);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200230
Alexander Hansen17f8e6a2025-06-10 12:10:09 +0200231 populateInterfacePropertyFromJson(systemConfiguration, path, key, value,
232 type, iface, permission);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200233 }
234 if (permission == sdbusplus::asio::PropertyPermission::readWrite)
235 {
Alexander Hansen89737252025-08-04 15:15:13 +0200236 createDeleteObjectMethod(jsonPointerPath, iface, systemConfiguration);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200237 }
238 tryIfaceInitialize(iface);
239}
240
Alexander Hansen57604ed2025-06-27 13:22:28 +0200241void EMDBusInterface::createAddObjectMethod(
Alexander Hansen89737252025-08-04 15:15:13 +0200242 const std::string& jsonPointerPath, const std::string& path,
243 nlohmann::json& systemConfiguration, const std::string& board)
Christopher Meis12bea9b2025-04-03 10:14:42 +0200244{
Alexander Hansen89737252025-08-04 15:15:13 +0200245 std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
246 createInterface(path, "xyz.openbmc_project.AddObject", board);
Christopher Meis12bea9b2025-04-03 10:14:42 +0200247
248 iface->register_method(
249 "AddObject",
Alexander Hansen89737252025-08-04 15:15:13 +0200250 [&systemConfiguration, jsonPointerPath{std::string(jsonPointerPath)},
251 path{std::string(path)}, board,
Alexander Hansen57604ed2025-06-27 13:22:28 +0200252 this](const boost::container::flat_map<std::string, JsonVariantType>&
253 data) {
Christopher Meis12bea9b2025-04-03 10:14:42 +0200254 nlohmann::json::json_pointer ptr(jsonPointerPath);
255 nlohmann::json& base = systemConfiguration[ptr];
256 auto findExposes = base.find("Exposes");
257
258 if (findExposes == base.end())
259 {
260 throw std::invalid_argument("Entity must have children.");
261 }
262
263 // this will throw invalid-argument to sdbusplus if invalid json
264 nlohmann::json newData{};
265 for (const auto& item : data)
266 {
267 nlohmann::json& newJson = newData[item.first];
268 std::visit(
269 [&newJson](auto&& val) {
270 newJson = std::forward<decltype(val)>(val);
271 },
272 item.second);
273 }
274
275 auto findName = newData.find("Name");
276 auto findType = newData.find("Type");
277 if (findName == newData.end() || findType == newData.end())
278 {
279 throw std::invalid_argument("AddObject missing Name or Type");
280 }
281 const std::string* type = findType->get_ptr<const std::string*>();
282 const std::string* name = findName->get_ptr<const std::string*>();
283 if (type == nullptr || name == nullptr)
284 {
285 throw std::invalid_argument("Type and Name must be a string.");
286 }
287
288 bool foundNull = false;
289 size_t lastIndex = 0;
290 // we add in the "exposes"
291 for (const auto& expose : *findExposes)
292 {
293 if (expose.is_null())
294 {
295 foundNull = true;
296 continue;
297 }
298
299 if (expose["Name"] == *name && expose["Type"] == *type)
300 {
301 throw std::invalid_argument(
302 "Field already in JSON, not adding");
303 }
304
305 if (foundNull)
306 {
307 continue;
308 }
309
310 lastIndex++;
311 }
312
Marc Olberdingd10dd462025-08-22 11:58:46 -0700313 if constexpr (ENABLE_RUNTIME_VALIDATE_JSON)
Christopher Meis12bea9b2025-04-03 10:14:42 +0200314 {
Marc Olberdingd10dd462025-08-22 11:58:46 -0700315 const std::filesystem::path schemaPath =
316 std::filesystem::path(schemaDirectory) /
317 "exposes_record.json";
318
319 std::ifstream schemaFile{schemaPath};
320
321 if (!schemaFile.good())
322 {
323 throw std::invalid_argument(
324 "No schema avaliable, cannot validate.");
325 }
326 nlohmann::json schema =
327 nlohmann::json::parse(schemaFile, nullptr, false, true);
328 if (schema.is_discarded())
329 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200330 lg2::error("Schema not legal: {TYPE}.json", "TYPE", *type);
Marc Olberdingd10dd462025-08-22 11:58:46 -0700331 throw DBusInternalError();
332 }
333
334 if (!validateJson(schema, newData))
335 {
336 throw std::invalid_argument("Data does not match schema");
337 }
Christopher Meis12bea9b2025-04-03 10:14:42 +0200338 }
Marc Olberdingd10dd462025-08-22 11:58:46 -0700339
Christopher Meis12bea9b2025-04-03 10:14:42 +0200340 if (foundNull)
341 {
342 findExposes->at(lastIndex) = newData;
343 }
344 else
345 {
346 findExposes->push_back(newData);
347 }
Christopher Meisf7252572025-06-11 13:22:05 +0200348 if (!writeJsonFiles(systemConfiguration))
Christopher Meis12bea9b2025-04-03 10:14:42 +0200349 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200350 lg2::error("Error writing json files");
Christopher Meis12bea9b2025-04-03 10:14:42 +0200351 }
352 std::string dbusName = *name;
353
354 std::regex_replace(dbusName.begin(), dbusName.begin(),
355 dbusName.end(), illegalDbusMemberRegex, "_");
356
357 std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
Alexander Hansen89737252025-08-04 15:15:13 +0200358 createInterface(path + "/" + dbusName,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200359 "xyz.openbmc_project.Configuration." + *type,
360 board, true);
361 // permission is read-write, as since we just created it, must be
362 // runtime modifiable
363 populateInterfaceFromJson(
Alexander Hansen89737252025-08-04 15:15:13 +0200364 systemConfiguration,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200365 jsonPointerPath + "/Exposes/" + std::to_string(lastIndex),
Alexander Hansen89737252025-08-04 15:15:13 +0200366 interface, newData,
Christopher Meis12bea9b2025-04-03 10:14:42 +0200367 sdbusplus::asio::PropertyPermission::readWrite);
368 });
369 tryIfaceInitialize(iface);
370}
371
372std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>&
Alexander Hansen57604ed2025-06-27 13:22:28 +0200373 EMDBusInterface::getDeviceInterfaces(const nlohmann::json& device)
Christopher Meis12bea9b2025-04-03 10:14:42 +0200374{
375 return inventory[device["Name"].get<std::string>()];
376}
377
378} // namespace dbus_interface