1 #include "dbus_interface.hpp" 2 3 #include "perform_probe.hpp" 4 #include "utils.hpp" 5 6 #include <phosphor-logging/lg2.hpp> 7 8 #include <flat_map> 9 #include <fstream> 10 #include <regex> 11 #include <string> 12 #include <vector> 13 14 namespace dbus_interface 15 { 16 17 const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]"); 18 const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]"); 19 20 EMDBusInterface::EMDBusInterface(boost::asio::io_context& io, 21 sdbusplus::asio::object_server& objServer) : 22 io(io), objServer(objServer) 23 {} 24 25 void tryIfaceInitialize(std::shared_ptr<sdbusplus::asio::dbus_interface>& iface) 26 { 27 try 28 { 29 iface->initialize(); 30 } 31 catch (std::exception& e) 32 { 33 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()); 37 } 38 } 39 40 std::shared_ptr<sdbusplus::asio::dbus_interface> 41 EMDBusInterface::createInterface(const std::string& path, 42 const std::string& interface, 43 const std::string& parent, bool checkNull) 44 { 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 65 void EMDBusInterface::createDeleteObjectMethod( 66 const std::string& jsonPointerPath, 67 const std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, 68 nlohmann::json& systemConfiguration) 69 { 70 std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface; 71 iface->register_method( 72 "Delete", [this, &systemConfiguration, interface, 73 jsonPointerPath{std::string(jsonPointerPath)}]() { 74 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 87 boost::asio::post(io, [dbusInterface, this]() mutable { 88 objServer.remove_interface(dbusInterface); 89 }); 90 91 if (!writeJsonFiles(systemConfiguration)) 92 { 93 lg2::error("error setting json file"); 94 throw DBusInternalError(); 95 } 96 }); 97 } 98 99 static 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 118 static nlohmann::json::value_t getDBusType( 119 const nlohmann::json& value, nlohmann::json::value_t type, 120 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 { 133 return nlohmann::json::value_t::number_float; 134 } 135 } 136 else if (value.is_number()) 137 { 138 return nlohmann::json::value_t::number_float; 139 } 140 } 141 142 return type; 143 } 144 145 static 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) 155 { 156 case (nlohmann::json::value_t::boolean): 157 { 158 addValueToDBus<bool>(key, value, *iface, permission, 159 systemConfiguration, path); 160 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 { 188 lg2::error( 189 "Unexpected json type in system configuration {KEY}: {VALUE}", 190 "KEY", key, "VALUE", value.type_name()); 191 break; 192 } 193 } 194 } 195 196 // adds simple json types to interface's properties 197 void EMDBusInterface::populateInterfaceFromJson( 198 nlohmann::json& systemConfiguration, const std::string& jsonPointerPath, 199 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, 200 nlohmann::json& dict, sdbusplus::asio::PropertyPermission permission) 201 { 202 for (const auto& [key, value] : dict.items()) 203 { 204 auto type = value.type(); 205 if (value.type() == nlohmann::json::value_t::array) 206 { 207 if (value.empty()) 208 { 209 continue; 210 } 211 type = value[0].type(); 212 if (!checkArrayElementsSameType(value)) 213 { 214 lg2::error("dbus format error {VALUE}", "VALUE", value); 215 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); 225 226 populateInterfacePropertyFromJson(systemConfiguration, path, key, value, 227 type, iface, permission); 228 } 229 if (permission == sdbusplus::asio::PropertyPermission::readWrite) 230 { 231 createDeleteObjectMethod(jsonPointerPath, iface, systemConfiguration); 232 } 233 tryIfaceInitialize(iface); 234 } 235 236 void EMDBusInterface::addObject( 237 const std::flat_map<std::string, JsonVariantType, std::less<>>& data, 238 nlohmann::json& systemConfiguration, const std::string& jsonPointerPath, 239 const std::string& path, const std::string& board) 240 { 241 nlohmann::json::json_pointer ptr(jsonPointerPath); 242 nlohmann::json& base = systemConfiguration[ptr]; 243 auto findExposes = base.find("Exposes"); 244 245 if (findExposes == base.end()) 246 { 247 throw std::invalid_argument("Entity must have children."); 248 } 249 250 // this will throw invalid-argument to sdbusplus if invalid json 251 nlohmann::json newData{}; 252 for (const auto& item : data) 253 { 254 nlohmann::json& newJson = newData[item.first]; 255 std::visit( 256 [&newJson](auto&& val) { 257 newJson = std::forward<decltype(val)>(val); 258 }, 259 item.second); 260 } 261 262 auto findName = newData.find("Name"); 263 auto findType = newData.find("Type"); 264 if (findName == newData.end() || findType == newData.end()) 265 { 266 throw std::invalid_argument("AddObject missing Name or Type"); 267 } 268 const std::string* type = findType->get_ptr<const std::string*>(); 269 const std::string* name = findName->get_ptr<const std::string*>(); 270 if (type == nullptr || name == nullptr) 271 { 272 throw std::invalid_argument("Type and Name must be a string."); 273 } 274 275 bool foundNull = false; 276 size_t lastIndex = 0; 277 // we add in the "exposes" 278 for (const auto& expose : *findExposes) 279 { 280 if (expose.is_null()) 281 { 282 foundNull = true; 283 continue; 284 } 285 286 if (expose["Name"] == *name && expose["Type"] == *type) 287 { 288 throw std::invalid_argument("Field already in JSON, not adding"); 289 } 290 291 if (foundNull) 292 { 293 continue; 294 } 295 296 lastIndex++; 297 } 298 299 if constexpr (ENABLE_RUNTIME_VALIDATE_JSON) 300 { 301 const std::filesystem::path schemaPath = 302 std::filesystem::path(schemaDirectory) / "exposes_record.json"; 303 304 std::ifstream schemaFile{schemaPath}; 305 306 if (!schemaFile.good()) 307 { 308 throw std::invalid_argument( 309 "No schema avaliable, cannot validate."); 310 } 311 nlohmann::json schema = 312 nlohmann::json::parse(schemaFile, nullptr, false, true); 313 if (schema.is_discarded()) 314 { 315 lg2::error("Schema not legal: {TYPE}.json", "TYPE", *type); 316 throw DBusInternalError(); 317 } 318 319 if (!validateJson(schema, newData)) 320 { 321 throw std::invalid_argument("Data does not match schema"); 322 } 323 } 324 325 if (foundNull) 326 { 327 findExposes->at(lastIndex) = newData; 328 } 329 else 330 { 331 findExposes->push_back(newData); 332 } 333 if (!writeJsonFiles(systemConfiguration)) 334 { 335 lg2::error("Error writing json files"); 336 } 337 std::string dbusName = *name; 338 339 std::regex_replace(dbusName.begin(), dbusName.begin(), dbusName.end(), 340 illegalDbusMemberRegex, "_"); 341 342 std::shared_ptr<sdbusplus::asio::dbus_interface> interface = 343 createInterface(path + "/" + dbusName, 344 "xyz.openbmc_project.Configuration." + *type, board, 345 true); 346 // permission is read-write, as since we just created it, must be 347 // runtime modifiable 348 populateInterfaceFromJson( 349 systemConfiguration, 350 jsonPointerPath + "/Exposes/" + std::to_string(lastIndex), interface, 351 newData, sdbusplus::asio::PropertyPermission::readWrite); 352 } 353 354 void EMDBusInterface::createAddObjectMethod( 355 const std::string& jsonPointerPath, const std::string& path, 356 nlohmann::json& systemConfiguration, const std::string& board) 357 { 358 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = 359 createInterface(path, "xyz.openbmc_project.AddObject", board); 360 361 iface->register_method( 362 "AddObject", 363 [&systemConfiguration, jsonPointerPath{std::string(jsonPointerPath)}, 364 path{std::string(path)}, board{std::string(board)}, 365 this](const std::flat_map<std::string, JsonVariantType, std::less<>>& 366 data) { 367 addObject(data, systemConfiguration, jsonPointerPath, path, board); 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