1 #include "dbus_interface.hpp" 2 3 #include "perform_probe.hpp" 4 #include "utils.hpp" 5 6 #include <boost/algorithm/string/case_conv.hpp> 7 #include <boost/container/flat_map.hpp> 8 #include <phosphor-logging/lg2.hpp> 9 10 #include <fstream> 11 #include <regex> 12 #include <string> 13 #include <vector> 14 15 using JsonVariantType = 16 std::variant<std::vector<std::string>, std::vector<double>, std::string, 17 int64_t, uint64_t, double, int32_t, uint32_t, int16_t, 18 uint16_t, uint8_t, bool>; 19 20 namespace dbus_interface 21 { 22 23 const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]"); 24 const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]"); 25 26 EMDBusInterface::EMDBusInterface(boost::asio::io_context& io, 27 sdbusplus::asio::object_server& objServer) : 28 io(io), objServer(objServer) 29 {} 30 31 void tryIfaceInitialize(std::shared_ptr<sdbusplus::asio::dbus_interface>& iface) 32 { 33 try 34 { 35 iface->initialize(); 36 } 37 catch (std::exception& e) 38 { 39 lg2::error( 40 "Unable to initialize dbus interface : {ERR} object Path : {PATH} interface name : {INTF}", 41 "ERR", e, "PATH", iface->get_object_path(), "INTF", 42 iface->get_interface_name()); 43 } 44 } 45 46 std::shared_ptr<sdbusplus::asio::dbus_interface> 47 EMDBusInterface::createInterface(const std::string& path, 48 const std::string& interface, 49 const std::string& parent, bool checkNull) 50 { 51 // on first add we have no reason to check for null before add, as there 52 // won't be any. For dynamically added interfaces, we check for null so that 53 // a constant delete/add will not create a memory leak 54 55 auto ptr = objServer.add_interface(path, interface); 56 auto& dataVector = inventory[parent]; 57 if (checkNull) 58 { 59 auto it = std::find_if(dataVector.begin(), dataVector.end(), 60 [](const auto& p) { return p.expired(); }); 61 if (it != dataVector.end()) 62 { 63 *it = ptr; 64 return ptr; 65 } 66 } 67 dataVector.emplace_back(ptr); 68 return ptr; 69 } 70 71 void EMDBusInterface::createDeleteObjectMethod( 72 const std::string& jsonPointerPath, 73 const std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, 74 nlohmann::json& systemConfiguration) 75 { 76 std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface; 77 iface->register_method( 78 "Delete", [this, &systemConfiguration, interface, 79 jsonPointerPath{std::string(jsonPointerPath)}]() { 80 std::shared_ptr<sdbusplus::asio::dbus_interface> dbusInterface = 81 interface.lock(); 82 if (!dbusInterface) 83 { 84 // this technically can't happen as the pointer is pointing to 85 // us 86 throw DBusInternalError(); 87 } 88 nlohmann::json::json_pointer ptr(jsonPointerPath); 89 systemConfiguration[ptr] = nullptr; 90 91 // todo(james): dig through sdbusplus to find out why we can't 92 // delete it in a method call 93 boost::asio::post(io, [dbusInterface, this]() mutable { 94 objServer.remove_interface(dbusInterface); 95 }); 96 97 if (!writeJsonFiles(systemConfiguration)) 98 { 99 lg2::error("error setting json file"); 100 throw DBusInternalError(); 101 } 102 }); 103 } 104 105 static bool checkArrayElementsSameType(nlohmann::json& value) 106 { 107 nlohmann::json::array_t* arr = value.get_ptr<nlohmann::json::array_t*>(); 108 if (arr == nullptr) 109 { 110 return false; 111 } 112 113 if (arr->empty()) 114 { 115 return true; 116 } 117 118 nlohmann::json::value_t firstType = value[0].type(); 119 return std::ranges::all_of(value, [firstType](const nlohmann::json& el) { 120 return el.type() == firstType; 121 }); 122 } 123 124 static nlohmann::json::value_t getDBusType( 125 const nlohmann::json& value, nlohmann::json::value_t type, 126 sdbusplus::asio::PropertyPermission permission) 127 { 128 const bool array = value.type() == nlohmann::json::value_t::array; 129 130 if (permission == sdbusplus::asio::PropertyPermission::readWrite) 131 { 132 // all setable numbers are doubles as it is difficult to always 133 // create a configuration file with all whole numbers as decimals 134 // i.e. 1.0 135 if (array) 136 { 137 if (value[0].is_number()) 138 { 139 return nlohmann::json::value_t::number_float; 140 } 141 } 142 else if (value.is_number()) 143 { 144 return nlohmann::json::value_t::number_float; 145 } 146 } 147 148 return type; 149 } 150 151 static void populateInterfacePropertyFromJson( 152 nlohmann::json& systemConfiguration, const std::string& path, 153 const nlohmann::json& key, const nlohmann::json& value, 154 nlohmann::json::value_t type, 155 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, 156 sdbusplus::asio::PropertyPermission permission) 157 { 158 const auto modifiedType = getDBusType(value, type, permission); 159 160 switch (modifiedType) 161 { 162 case (nlohmann::json::value_t::boolean): 163 { 164 addValueToDBus<bool>(key, value, *iface, permission, 165 systemConfiguration, path); 166 break; 167 } 168 case (nlohmann::json::value_t::number_integer): 169 { 170 addValueToDBus<int64_t>(key, value, *iface, permission, 171 systemConfiguration, path); 172 break; 173 } 174 case (nlohmann::json::value_t::number_unsigned): 175 { 176 addValueToDBus<uint64_t>(key, value, *iface, permission, 177 systemConfiguration, path); 178 break; 179 } 180 case (nlohmann::json::value_t::number_float): 181 { 182 addValueToDBus<double>(key, value, *iface, permission, 183 systemConfiguration, path); 184 break; 185 } 186 case (nlohmann::json::value_t::string): 187 { 188 addValueToDBus<std::string>(key, value, *iface, permission, 189 systemConfiguration, path); 190 break; 191 } 192 default: 193 { 194 lg2::error( 195 "Unexpected json type in system configuration {KEY}: {VALUE}", 196 "KEY", key, "VALUE", value.type_name()); 197 break; 198 } 199 } 200 } 201 202 // adds simple json types to interface's properties 203 void EMDBusInterface::populateInterfaceFromJson( 204 nlohmann::json& systemConfiguration, const std::string& jsonPointerPath, 205 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, 206 nlohmann::json& dict, sdbusplus::asio::PropertyPermission permission) 207 { 208 for (const auto& [key, value] : dict.items()) 209 { 210 auto type = value.type(); 211 if (value.type() == nlohmann::json::value_t::array) 212 { 213 if (value.empty()) 214 { 215 continue; 216 } 217 type = value[0].type(); 218 if (!checkArrayElementsSameType(value)) 219 { 220 lg2::error("dbus format error {VALUE}", "VALUE", value); 221 continue; 222 } 223 } 224 if (type == nlohmann::json::value_t::object) 225 { 226 continue; // handled elsewhere 227 } 228 229 std::string path = jsonPointerPath; 230 path.append("/").append(key); 231 232 populateInterfacePropertyFromJson(systemConfiguration, path, key, value, 233 type, iface, permission); 234 } 235 if (permission == sdbusplus::asio::PropertyPermission::readWrite) 236 { 237 createDeleteObjectMethod(jsonPointerPath, iface, systemConfiguration); 238 } 239 tryIfaceInitialize(iface); 240 } 241 242 void EMDBusInterface::createAddObjectMethod( 243 const std::string& jsonPointerPath, const std::string& path, 244 nlohmann::json& systemConfiguration, const std::string& board) 245 { 246 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = 247 createInterface(path, "xyz.openbmc_project.AddObject", board); 248 249 iface->register_method( 250 "AddObject", 251 [&systemConfiguration, jsonPointerPath{std::string(jsonPointerPath)}, 252 path{std::string(path)}, board, 253 this](const boost::container::flat_map<std::string, JsonVariantType>& 254 data) { 255 nlohmann::json::json_pointer ptr(jsonPointerPath); 256 nlohmann::json& base = systemConfiguration[ptr]; 257 auto findExposes = base.find("Exposes"); 258 259 if (findExposes == base.end()) 260 { 261 throw std::invalid_argument("Entity must have children."); 262 } 263 264 // this will throw invalid-argument to sdbusplus if invalid json 265 nlohmann::json newData{}; 266 for (const auto& item : data) 267 { 268 nlohmann::json& newJson = newData[item.first]; 269 std::visit( 270 [&newJson](auto&& val) { 271 newJson = std::forward<decltype(val)>(val); 272 }, 273 item.second); 274 } 275 276 auto findName = newData.find("Name"); 277 auto findType = newData.find("Type"); 278 if (findName == newData.end() || findType == newData.end()) 279 { 280 throw std::invalid_argument("AddObject missing Name or Type"); 281 } 282 const std::string* type = findType->get_ptr<const std::string*>(); 283 const std::string* name = findName->get_ptr<const std::string*>(); 284 if (type == nullptr || name == nullptr) 285 { 286 throw std::invalid_argument("Type and Name must be a string."); 287 } 288 289 bool foundNull = false; 290 size_t lastIndex = 0; 291 // we add in the "exposes" 292 for (const auto& expose : *findExposes) 293 { 294 if (expose.is_null()) 295 { 296 foundNull = true; 297 continue; 298 } 299 300 if (expose["Name"] == *name && expose["Type"] == *type) 301 { 302 throw std::invalid_argument( 303 "Field already in JSON, not adding"); 304 } 305 306 if (foundNull) 307 { 308 continue; 309 } 310 311 lastIndex++; 312 } 313 314 if constexpr (ENABLE_RUNTIME_VALIDATE_JSON) 315 { 316 const std::filesystem::path schemaPath = 317 std::filesystem::path(schemaDirectory) / 318 "exposes_record.json"; 319 320 std::ifstream schemaFile{schemaPath}; 321 322 if (!schemaFile.good()) 323 { 324 throw std::invalid_argument( 325 "No schema avaliable, cannot validate."); 326 } 327 nlohmann::json schema = 328 nlohmann::json::parse(schemaFile, nullptr, false, true); 329 if (schema.is_discarded()) 330 { 331 lg2::error("Schema not legal: {TYPE}.json", "TYPE", *type); 332 throw DBusInternalError(); 333 } 334 335 if (!validateJson(schema, newData)) 336 { 337 throw std::invalid_argument("Data does not match schema"); 338 } 339 } 340 341 if (foundNull) 342 { 343 findExposes->at(lastIndex) = newData; 344 } 345 else 346 { 347 findExposes->push_back(newData); 348 } 349 if (!writeJsonFiles(systemConfiguration)) 350 { 351 lg2::error("Error writing json files"); 352 } 353 std::string dbusName = *name; 354 355 std::regex_replace(dbusName.begin(), dbusName.begin(), 356 dbusName.end(), illegalDbusMemberRegex, "_"); 357 358 std::shared_ptr<sdbusplus::asio::dbus_interface> interface = 359 createInterface(path + "/" + dbusName, 360 "xyz.openbmc_project.Configuration." + *type, 361 board, true); 362 // permission is read-write, as since we just created it, must be 363 // runtime modifiable 364 populateInterfaceFromJson( 365 systemConfiguration, 366 jsonPointerPath + "/Exposes/" + std::to_string(lastIndex), 367 interface, newData, 368 sdbusplus::asio::PropertyPermission::readWrite); 369 }); 370 tryIfaceInitialize(iface); 371 } 372 373 std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>& 374 EMDBusInterface::getDeviceInterfaces(const nlohmann::json& device) 375 { 376 return inventory[device["Name"].get<std::string>()]; 377 } 378 379 } // namespace dbus_interface 380