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 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 std::cerr << "Unable to initialize dbus interface : " << e.what() 39 << "\n" 40 << "object Path : " << iface->get_object_path() << "\n" 41 << "interface name : " << iface->get_interface_name() << "\n"; 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 std::cerr << "error setting json file\n"; 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 std::cerr << "Unexpected json type in system configuration " << key 194 << ": " << value.type_name() << "\n"; 195 break; 196 } 197 } 198 } 199 200 // adds simple json types to interface's properties 201 void EMDBusInterface::populateInterfaceFromJson( 202 nlohmann::json& systemConfiguration, const std::string& jsonPointerPath, 203 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, 204 nlohmann::json& dict, sdbusplus::asio::PropertyPermission permission) 205 { 206 for (const auto& [key, value] : dict.items()) 207 { 208 auto type = value.type(); 209 if (value.type() == nlohmann::json::value_t::array) 210 { 211 if (value.empty()) 212 { 213 continue; 214 } 215 type = value[0].type(); 216 if (!checkArrayElementsSameType(value)) 217 { 218 std::cerr << "dbus format error" << value << "\n"; 219 continue; 220 } 221 } 222 if (type == nlohmann::json::value_t::object) 223 { 224 continue; // handled elsewhere 225 } 226 227 std::string path = jsonPointerPath; 228 path.append("/").append(key); 229 230 populateInterfacePropertyFromJson(systemConfiguration, path, key, value, 231 type, iface, permission); 232 } 233 if (permission == sdbusplus::asio::PropertyPermission::readWrite) 234 { 235 createDeleteObjectMethod(jsonPointerPath, iface, systemConfiguration); 236 } 237 tryIfaceInitialize(iface); 238 } 239 240 void EMDBusInterface::createAddObjectMethod( 241 const std::string& jsonPointerPath, const std::string& path, 242 nlohmann::json& systemConfiguration, const std::string& board) 243 { 244 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = 245 createInterface(path, "xyz.openbmc_project.AddObject", board); 246 247 iface->register_method( 248 "AddObject", 249 [&systemConfiguration, jsonPointerPath{std::string(jsonPointerPath)}, 250 path{std::string(path)}, board, 251 this](const boost::container::flat_map<std::string, JsonVariantType>& 252 data) { 253 nlohmann::json::json_pointer ptr(jsonPointerPath); 254 nlohmann::json& base = systemConfiguration[ptr]; 255 auto findExposes = base.find("Exposes"); 256 257 if (findExposes == base.end()) 258 { 259 throw std::invalid_argument("Entity must have children."); 260 } 261 262 // this will throw invalid-argument to sdbusplus if invalid json 263 nlohmann::json newData{}; 264 for (const auto& item : data) 265 { 266 nlohmann::json& newJson = newData[item.first]; 267 std::visit( 268 [&newJson](auto&& val) { 269 newJson = std::forward<decltype(val)>(val); 270 }, 271 item.second); 272 } 273 274 auto findName = newData.find("Name"); 275 auto findType = newData.find("Type"); 276 if (findName == newData.end() || findType == newData.end()) 277 { 278 throw std::invalid_argument("AddObject missing Name or Type"); 279 } 280 const std::string* type = findType->get_ptr<const std::string*>(); 281 const std::string* name = findName->get_ptr<const std::string*>(); 282 if (type == nullptr || name == nullptr) 283 { 284 throw std::invalid_argument("Type and Name must be a string."); 285 } 286 287 bool foundNull = false; 288 size_t lastIndex = 0; 289 // we add in the "exposes" 290 for (const auto& expose : *findExposes) 291 { 292 if (expose.is_null()) 293 { 294 foundNull = true; 295 continue; 296 } 297 298 if (expose["Name"] == *name && expose["Type"] == *type) 299 { 300 throw std::invalid_argument( 301 "Field already in JSON, not adding"); 302 } 303 304 if (foundNull) 305 { 306 continue; 307 } 308 309 lastIndex++; 310 } 311 312 if constexpr (ENABLE_RUNTIME_VALIDATE_JSON) 313 { 314 const std::filesystem::path schemaPath = 315 std::filesystem::path(schemaDirectory) / 316 "exposes_record.json"; 317 318 std::ifstream schemaFile{schemaPath}; 319 320 if (!schemaFile.good()) 321 { 322 throw std::invalid_argument( 323 "No schema avaliable, cannot validate."); 324 } 325 nlohmann::json schema = 326 nlohmann::json::parse(schemaFile, nullptr, false, true); 327 if (schema.is_discarded()) 328 { 329 std::cerr << "Schema not legal" << *type << ".json\n"; 330 throw DBusInternalError(); 331 } 332 333 if (!validateJson(schema, newData)) 334 { 335 throw std::invalid_argument("Data does not match schema"); 336 } 337 } 338 339 if (foundNull) 340 { 341 findExposes->at(lastIndex) = newData; 342 } 343 else 344 { 345 findExposes->push_back(newData); 346 } 347 if (!writeJsonFiles(systemConfiguration)) 348 { 349 std::cerr << "Error writing json files\n"; 350 } 351 std::string dbusName = *name; 352 353 std::regex_replace(dbusName.begin(), dbusName.begin(), 354 dbusName.end(), illegalDbusMemberRegex, "_"); 355 356 std::shared_ptr<sdbusplus::asio::dbus_interface> interface = 357 createInterface(path + "/" + dbusName, 358 "xyz.openbmc_project.Configuration." + *type, 359 board, true); 360 // permission is read-write, as since we just created it, must be 361 // runtime modifiable 362 populateInterfaceFromJson( 363 systemConfiguration, 364 jsonPointerPath + "/Exposes/" + std::to_string(lastIndex), 365 interface, newData, 366 sdbusplus::asio::PropertyPermission::readWrite); 367 }); 368 tryIfaceInitialize(iface); 369 } 370 371 std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>& 372 EMDBusInterface::getDeviceInterfaces(const nlohmann::json& device) 373 { 374 return inventory[device["Name"].get<std::string>()]; 375 } 376 377 } // namespace dbus_interface 378