1 #include "dbus_interface.hpp" 2 3 #include "perform_probe.hpp" 4 #include "utils.hpp" 5 6 #include <boost/container/flat_map.hpp> 7 #include <phosphor-logging/lg2.hpp> 8 9 #include <fstream> 10 #include <regex> 11 #include <string> 12 #include <vector> 13 14 using 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 19 namespace dbus_interface 20 { 21 22 const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]"); 23 const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]"); 24 25 EMDBusInterface::EMDBusInterface(boost::asio::io_context& io, 26 sdbusplus::asio::object_server& objServer) : 27 io(io), objServer(objServer) 28 {} 29 30 void tryIfaceInitialize(std::shared_ptr<sdbusplus::asio::dbus_interface>& iface) 31 { 32 try 33 { 34 iface->initialize(); 35 } 36 catch (std::exception& e) 37 { 38 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()); 42 } 43 } 44 45 std::shared_ptr<sdbusplus::asio::dbus_interface> 46 EMDBusInterface::createInterface(const std::string& path, 47 const std::string& interface, 48 const std::string& parent, bool checkNull) 49 { 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 70 void EMDBusInterface::createDeleteObjectMethod( 71 const std::string& jsonPointerPath, 72 const std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, 73 nlohmann::json& systemConfiguration) 74 { 75 std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface; 76 iface->register_method( 77 "Delete", [this, &systemConfiguration, interface, 78 jsonPointerPath{std::string(jsonPointerPath)}]() { 79 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 92 boost::asio::post(io, [dbusInterface, this]() mutable { 93 objServer.remove_interface(dbusInterface); 94 }); 95 96 if (!writeJsonFiles(systemConfiguration)) 97 { 98 lg2::error("error setting json file"); 99 throw DBusInternalError(); 100 } 101 }); 102 } 103 104 static 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 123 static nlohmann::json::value_t getDBusType( 124 const nlohmann::json& value, nlohmann::json::value_t type, 125 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 { 138 return nlohmann::json::value_t::number_float; 139 } 140 } 141 else if (value.is_number()) 142 { 143 return nlohmann::json::value_t::number_float; 144 } 145 } 146 147 return type; 148 } 149 150 static 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) 160 { 161 case (nlohmann::json::value_t::boolean): 162 { 163 addValueToDBus<bool>(key, value, *iface, permission, 164 systemConfiguration, path); 165 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 { 193 lg2::error( 194 "Unexpected json type in system configuration {KEY}: {VALUE}", 195 "KEY", key, "VALUE", value.type_name()); 196 break; 197 } 198 } 199 } 200 201 // adds simple json types to interface's properties 202 void EMDBusInterface::populateInterfaceFromJson( 203 nlohmann::json& systemConfiguration, const std::string& jsonPointerPath, 204 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, 205 nlohmann::json& dict, sdbusplus::asio::PropertyPermission permission) 206 { 207 for (const auto& [key, value] : dict.items()) 208 { 209 auto type = value.type(); 210 if (value.type() == nlohmann::json::value_t::array) 211 { 212 if (value.empty()) 213 { 214 continue; 215 } 216 type = value[0].type(); 217 if (!checkArrayElementsSameType(value)) 218 { 219 lg2::error("dbus format error {VALUE}", "VALUE", value); 220 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); 230 231 populateInterfacePropertyFromJson(systemConfiguration, path, key, value, 232 type, iface, permission); 233 } 234 if (permission == sdbusplus::asio::PropertyPermission::readWrite) 235 { 236 createDeleteObjectMethod(jsonPointerPath, iface, systemConfiguration); 237 } 238 tryIfaceInitialize(iface); 239 } 240 241 void EMDBusInterface::createAddObjectMethod( 242 const std::string& jsonPointerPath, const std::string& path, 243 nlohmann::json& systemConfiguration, const std::string& board) 244 { 245 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = 246 createInterface(path, "xyz.openbmc_project.AddObject", board); 247 248 iface->register_method( 249 "AddObject", 250 [&systemConfiguration, jsonPointerPath{std::string(jsonPointerPath)}, 251 path{std::string(path)}, board, 252 this](const boost::container::flat_map<std::string, JsonVariantType>& 253 data) { 254 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 313 if constexpr (ENABLE_RUNTIME_VALIDATE_JSON) 314 { 315 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 { 330 lg2::error("Schema not legal: {TYPE}.json", "TYPE", *type); 331 throw DBusInternalError(); 332 } 333 334 if (!validateJson(schema, newData)) 335 { 336 throw std::invalid_argument("Data does not match schema"); 337 } 338 } 339 340 if (foundNull) 341 { 342 findExposes->at(lastIndex) = newData; 343 } 344 else 345 { 346 findExposes->push_back(newData); 347 } 348 if (!writeJsonFiles(systemConfiguration)) 349 { 350 lg2::error("Error writing json files"); 351 } 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 = 358 createInterface(path + "/" + dbusName, 359 "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( 364 systemConfiguration, 365 jsonPointerPath + "/Exposes/" + std::to_string(lastIndex), 366 interface, newData, 367 sdbusplus::asio::PropertyPermission::readWrite); 368 }); 369 tryIfaceInitialize(iface); 370 } 371 372 std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>& 373 EMDBusInterface::getDeviceInterfaces(const nlohmann::json& device) 374 { 375 return inventory[device["Name"].get<std::string>()]; 376 } 377 378 } // namespace dbus_interface 379