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 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 std::cerr << "Unable to initialize dbus interface : " << e.what() 34 << "\n" 35 << "object Path : " << iface->get_object_path() << "\n" 36 << "interface name : " << iface->get_interface_name() << "\n"; 37 } 38 } 39 40 std::shared_ptr<sdbusplus::asio::dbus_interface> 41 EMDBusInterface::createInterface( 42 sdbusplus::asio::object_server& objServer, const std::string& path, 43 const std::string& interface, 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 createDeleteObjectMethod( 66 const std::string& jsonPointerPath, 67 const std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, 68 sdbusplus::asio::object_server& objServer, 69 nlohmann::json& systemConfiguration, boost::asio::io_context& io) 70 { 71 std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface; 72 iface->register_method( 73 "Delete", [&objServer, &systemConfiguration, interface, 74 jsonPointerPath{std::string(jsonPointerPath)}, &io]() { 75 std::shared_ptr<sdbusplus::asio::dbus_interface> dbusInterface = 76 interface.lock(); 77 if (!dbusInterface) 78 { 79 // this technically can't happen as the pointer is pointing to 80 // us 81 throw DBusInternalError(); 82 } 83 nlohmann::json::json_pointer ptr(jsonPointerPath); 84 systemConfiguration[ptr] = nullptr; 85 86 // todo(james): dig through sdbusplus to find out why we can't 87 // delete it in a method call 88 boost::asio::post(io, [&objServer, dbusInterface]() mutable { 89 objServer.remove_interface(dbusInterface); 90 }); 91 92 if (!writeJsonFiles(systemConfiguration)) 93 { 94 std::cerr << "error setting json file\n"; 95 throw DBusInternalError(); 96 } 97 }); 98 } 99 100 static bool checkArrayElementsSameType(nlohmann::json& value) 101 { 102 nlohmann::json::array_t* arr = value.get_ptr<nlohmann::json::array_t*>(); 103 if (arr == nullptr) 104 { 105 return false; 106 } 107 108 if (arr->empty()) 109 { 110 return true; 111 } 112 113 nlohmann::json::value_t firstType = value[0].type(); 114 return std::ranges::all_of(value, [firstType](const nlohmann::json& el) { 115 return el.type() == firstType; 116 }); 117 } 118 119 static nlohmann::json::value_t getDBusType( 120 const nlohmann::json& value, nlohmann::json::value_t type, 121 sdbusplus::asio::PropertyPermission permission) 122 { 123 const bool array = value.type() == nlohmann::json::value_t::array; 124 125 if (permission == sdbusplus::asio::PropertyPermission::readWrite) 126 { 127 // all setable numbers are doubles as it is difficult to always 128 // create a configuration file with all whole numbers as decimals 129 // i.e. 1.0 130 if (array) 131 { 132 if (value[0].is_number()) 133 { 134 return nlohmann::json::value_t::number_float; 135 } 136 } 137 else if (value.is_number()) 138 { 139 return nlohmann::json::value_t::number_float; 140 } 141 } 142 143 return type; 144 } 145 146 static void populateInterfacePropertyFromJson( 147 nlohmann::json& systemConfiguration, const std::string& path, 148 const nlohmann::json& key, const nlohmann::json& value, 149 nlohmann::json::value_t type, 150 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, 151 sdbusplus::asio::PropertyPermission permission) 152 { 153 const auto modifiedType = getDBusType(value, type, permission); 154 155 switch (modifiedType) 156 { 157 case (nlohmann::json::value_t::boolean): 158 { 159 // todo: array of bool isn't detected correctly by 160 // sdbusplus, change it to numbers 161 addValueToDBus<uint64_t, bool>(key, value, *iface, permission, 162 systemConfiguration, path); 163 break; 164 } 165 case (nlohmann::json::value_t::number_integer): 166 { 167 addValueToDBus<int64_t>(key, value, *iface, permission, 168 systemConfiguration, path); 169 break; 170 } 171 case (nlohmann::json::value_t::number_unsigned): 172 { 173 addValueToDBus<uint64_t>(key, value, *iface, permission, 174 systemConfiguration, path); 175 break; 176 } 177 case (nlohmann::json::value_t::number_float): 178 { 179 addValueToDBus<double>(key, value, *iface, permission, 180 systemConfiguration, path); 181 break; 182 } 183 case (nlohmann::json::value_t::string): 184 { 185 addValueToDBus<std::string>(key, value, *iface, permission, 186 systemConfiguration, path); 187 break; 188 } 189 default: 190 { 191 std::cerr << "Unexpected json type in system configuration " << key 192 << ": " << value.type_name() << "\n"; 193 break; 194 } 195 } 196 } 197 198 // adds simple json types to interface's properties 199 void populateInterfaceFromJson( 200 boost::asio::io_context& io, nlohmann::json& systemConfiguration, 201 const std::string& jsonPointerPath, 202 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, 203 nlohmann::json& dict, sdbusplus::asio::object_server& objServer, 204 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, objServer, 236 systemConfiguration, io); 237 } 238 tryIfaceInitialize(iface); 239 } 240 241 void EMDBusInterface::createAddObjectMethod( 242 boost::asio::io_context& io, const std::string& jsonPointerPath, 243 const std::string& path, nlohmann::json& systemConfiguration, 244 sdbusplus::asio::object_server& objServer, const std::string& board) 245 { 246 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = createInterface( 247 objServer, path, "xyz.openbmc_project.AddObject", board); 248 249 iface->register_method( 250 "AddObject", 251 [&systemConfiguration, &objServer, 252 jsonPointerPath{std::string(jsonPointerPath)}, path{std::string(path)}, 253 board, &io, 254 this](const boost::container::flat_map<std::string, JsonVariantType>& 255 data) { 256 nlohmann::json::json_pointer ptr(jsonPointerPath); 257 nlohmann::json& base = systemConfiguration[ptr]; 258 auto findExposes = base.find("Exposes"); 259 260 if (findExposes == base.end()) 261 { 262 throw std::invalid_argument("Entity must have children."); 263 } 264 265 // this will throw invalid-argument to sdbusplus if invalid json 266 nlohmann::json newData{}; 267 for (const auto& item : data) 268 { 269 nlohmann::json& newJson = newData[item.first]; 270 std::visit( 271 [&newJson](auto&& val) { 272 newJson = std::forward<decltype(val)>(val); 273 }, 274 item.second); 275 } 276 277 auto findName = newData.find("Name"); 278 auto findType = newData.find("Type"); 279 if (findName == newData.end() || findType == newData.end()) 280 { 281 throw std::invalid_argument("AddObject missing Name or Type"); 282 } 283 const std::string* type = findType->get_ptr<const std::string*>(); 284 const std::string* name = findName->get_ptr<const std::string*>(); 285 if (type == nullptr || name == nullptr) 286 { 287 throw std::invalid_argument("Type and Name must be a string."); 288 } 289 290 bool foundNull = false; 291 size_t lastIndex = 0; 292 // we add in the "exposes" 293 for (const auto& expose : *findExposes) 294 { 295 if (expose.is_null()) 296 { 297 foundNull = true; 298 continue; 299 } 300 301 if (expose["Name"] == *name && expose["Type"] == *type) 302 { 303 throw std::invalid_argument( 304 "Field already in JSON, not adding"); 305 } 306 307 if (foundNull) 308 { 309 continue; 310 } 311 312 lastIndex++; 313 } 314 315 std::ifstream schemaFile(std::string(schemaDirectory) + "/" + 316 boost::to_lower_copy(*type) + ".json"); 317 // todo(james) we might want to also make a list of 'can add' 318 // interfaces but for now I think the assumption if there is a 319 // schema avaliable that it is allowed to update is fine 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 if (!validateJson(schema, newData)) 333 { 334 throw std::invalid_argument("Data does not match schema"); 335 } 336 if (foundNull) 337 { 338 findExposes->at(lastIndex) = newData; 339 } 340 else 341 { 342 findExposes->push_back(newData); 343 } 344 if (!writeJsonFiles(systemConfiguration)) 345 { 346 std::cerr << "Error writing json files\n"; 347 throw DBusInternalError(); 348 } 349 std::string dbusName = *name; 350 351 std::regex_replace(dbusName.begin(), dbusName.begin(), 352 dbusName.end(), illegalDbusMemberRegex, "_"); 353 354 std::shared_ptr<sdbusplus::asio::dbus_interface> interface = 355 createInterface(objServer, path + "/" + dbusName, 356 "xyz.openbmc_project.Configuration." + *type, 357 board, true); 358 // permission is read-write, as since we just created it, must be 359 // runtime modifiable 360 populateInterfaceFromJson( 361 io, systemConfiguration, 362 jsonPointerPath + "/Exposes/" + std::to_string(lastIndex), 363 interface, newData, objServer, 364 sdbusplus::asio::PropertyPermission::readWrite); 365 }); 366 tryIfaceInitialize(iface); 367 } 368 369 std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>& 370 EMDBusInterface::getDeviceInterfaces(const nlohmann::json& device) 371 { 372 return inventory[device["Name"].get<std::string>()]; 373 } 374 375 } // namespace dbus_interface 376