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 // todo: array of bool isn't detected correctly by 164 // sdbusplus, change it to numbers 165 addValueToDBus<uint64_t, bool>(key, value, *iface, permission, 166 systemConfiguration, path); 167 break; 168 } 169 case (nlohmann::json::value_t::number_integer): 170 { 171 addValueToDBus<int64_t>(key, value, *iface, permission, 172 systemConfiguration, path); 173 break; 174 } 175 case (nlohmann::json::value_t::number_unsigned): 176 { 177 addValueToDBus<uint64_t>(key, value, *iface, permission, 178 systemConfiguration, path); 179 break; 180 } 181 case (nlohmann::json::value_t::number_float): 182 { 183 addValueToDBus<double>(key, value, *iface, permission, 184 systemConfiguration, path); 185 break; 186 } 187 case (nlohmann::json::value_t::string): 188 { 189 addValueToDBus<std::string>(key, value, *iface, permission, 190 systemConfiguration, path); 191 break; 192 } 193 default: 194 { 195 std::cerr << "Unexpected json type in system configuration " << key 196 << ": " << value.type_name() << "\n"; 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 std::cerr << "dbus format error" << value << "\n"; 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 std::ifstream schemaFile(std::string(schemaDirectory) + "/" + 315 boost::to_lower_copy(*type) + ".json"); 316 // todo(james) we might want to also make a list of 'can add' 317 // interfaces but for now I think the assumption if there is a 318 // schema avaliable that it is allowed to update is fine 319 if (!schemaFile.good()) 320 { 321 throw std::invalid_argument( 322 "No schema avaliable, cannot validate."); 323 } 324 nlohmann::json schema = 325 nlohmann::json::parse(schemaFile, nullptr, false, true); 326 if (schema.is_discarded()) 327 { 328 std::cerr << "Schema not legal" << *type << ".json\n"; 329 throw DBusInternalError(); 330 } 331 if (!validateJson(schema, newData)) 332 { 333 throw std::invalid_argument("Data does not match schema"); 334 } 335 if (foundNull) 336 { 337 findExposes->at(lastIndex) = newData; 338 } 339 else 340 { 341 findExposes->push_back(newData); 342 } 343 if (!writeJsonFiles(systemConfiguration)) 344 { 345 std::cerr << "Error writing json files\n"; 346 } 347 std::string dbusName = *name; 348 349 std::regex_replace(dbusName.begin(), dbusName.begin(), 350 dbusName.end(), illegalDbusMemberRegex, "_"); 351 352 std::shared_ptr<sdbusplus::asio::dbus_interface> interface = 353 createInterface(path + "/" + dbusName, 354 "xyz.openbmc_project.Configuration." + *type, 355 board, true); 356 // permission is read-write, as since we just created it, must be 357 // runtime modifiable 358 populateInterfaceFromJson( 359 systemConfiguration, 360 jsonPointerPath + "/Exposes/" + std::to_string(lastIndex), 361 interface, newData, 362 sdbusplus::asio::PropertyPermission::readWrite); 363 }); 364 tryIfaceInitialize(iface); 365 } 366 367 std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>& 368 EMDBusInterface::getDeviceInterfaces(const nlohmann::json& device) 369 { 370 return inventory[device["Name"].get<std::string>()]; 371 } 372 373 } // namespace dbus_interface 374