1 #include "dbus_interface.hpp" 2 3 #include "../utils.hpp" 4 #include "perform_probe.hpp" 5 6 #include <boost/algorithm/string/case_conv.hpp> 7 #include <boost/container/flat_map.hpp> 8 9 #include <regex> 10 #include <string> 11 #include <vector> 12 13 using JsonVariantType = 14 std::variant<std::vector<std::string>, std::vector<double>, std::string, 15 int64_t, uint64_t, double, int32_t, uint32_t, int16_t, 16 uint16_t, uint8_t, bool>; 17 18 namespace dbus_interface 19 { 20 21 const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]"); 22 const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]"); 23 24 // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) 25 // store reference to all interfaces so we can destroy them later 26 boost::container::flat_map< 27 std::string, std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>> 28 inventory; 29 // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) 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 std::cerr << "Unable to initialize dbus interface : " << e.what() 40 << "\n" 41 << "object Path : " << iface->get_object_path() << "\n" 42 << "interface name : " << iface->get_interface_name() << "\n"; 43 } 44 } 45 46 std::shared_ptr<sdbusplus::asio::dbus_interface> createInterface( 47 sdbusplus::asio::object_server& objServer, const std::string& path, 48 const std::string& interface, 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 createDeleteObjectMethod( 71 const std::string& jsonPointerPath, 72 const std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, 73 sdbusplus::asio::object_server& objServer, 74 nlohmann::json& systemConfiguration) 75 { 76 std::weak_ptr<sdbusplus::asio::dbus_interface> interface = iface; 77 iface->register_method( 78 "Delete", [&objServer, &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, [&objServer, dbusInterface]() mutable { 94 objServer.remove_interface(dbusInterface); 95 }); 96 97 if (!configuration::writeJsonFiles(systemConfiguration)) 98 { 99 std::cerr << "error setting json file\n"; 100 throw DBusInternalError(); 101 } 102 }); 103 } 104 105 // adds simple json types to interface's properties 106 void populateInterfaceFromJson( 107 nlohmann::json& systemConfiguration, const std::string& jsonPointerPath, 108 std::shared_ptr<sdbusplus::asio::dbus_interface>& iface, 109 nlohmann::json& dict, sdbusplus::asio::object_server& objServer, 110 sdbusplus::asio::PropertyPermission permission) 111 { 112 for (const auto& [key, value] : dict.items()) 113 { 114 auto type = value.type(); 115 bool array = false; 116 if (value.type() == nlohmann::json::value_t::array) 117 { 118 array = true; 119 if (value.empty()) 120 { 121 continue; 122 } 123 type = value[0].type(); 124 bool isLegal = true; 125 for (const auto& arrayItem : value) 126 { 127 if (arrayItem.type() != type) 128 { 129 isLegal = false; 130 break; 131 } 132 } 133 if (!isLegal) 134 { 135 std::cerr << "dbus format error" << value << "\n"; 136 continue; 137 } 138 } 139 if (type == nlohmann::json::value_t::object) 140 { 141 continue; // handled elsewhere 142 } 143 144 std::string path = jsonPointerPath; 145 path.append("/").append(key); 146 if (permission == sdbusplus::asio::PropertyPermission::readWrite) 147 { 148 // all setable numbers are doubles as it is difficult to always 149 // create a configuration file with all whole numbers as decimals 150 // i.e. 1.0 151 if (array) 152 { 153 if (value[0].is_number()) 154 { 155 type = nlohmann::json::value_t::number_float; 156 } 157 } 158 else if (value.is_number()) 159 { 160 type = nlohmann::json::value_t::number_float; 161 } 162 } 163 164 switch (type) 165 { 166 case (nlohmann::json::value_t::boolean): 167 { 168 if (array) 169 { 170 // todo: array of bool isn't detected correctly by 171 // sdbusplus, change it to numbers 172 addArrayToDbus<uint64_t>(key, value, iface.get(), 173 permission, systemConfiguration, 174 path); 175 } 176 177 else 178 { 179 addProperty(key, value.get<bool>(), iface.get(), 180 systemConfiguration, path, permission); 181 } 182 break; 183 } 184 case (nlohmann::json::value_t::number_integer): 185 { 186 if (array) 187 { 188 addArrayToDbus<int64_t>(key, value, iface.get(), permission, 189 systemConfiguration, path); 190 } 191 else 192 { 193 addProperty(key, value.get<int64_t>(), iface.get(), 194 systemConfiguration, path, 195 sdbusplus::asio::PropertyPermission::readOnly); 196 } 197 break; 198 } 199 case (nlohmann::json::value_t::number_unsigned): 200 { 201 if (array) 202 { 203 addArrayToDbus<uint64_t>(key, value, iface.get(), 204 permission, systemConfiguration, 205 path); 206 } 207 else 208 { 209 addProperty(key, value.get<uint64_t>(), iface.get(), 210 systemConfiguration, path, 211 sdbusplus::asio::PropertyPermission::readOnly); 212 } 213 break; 214 } 215 case (nlohmann::json::value_t::number_float): 216 { 217 if (array) 218 { 219 addArrayToDbus<double>(key, value, iface.get(), permission, 220 systemConfiguration, path); 221 } 222 223 else 224 { 225 addProperty(key, value.get<double>(), iface.get(), 226 systemConfiguration, path, permission); 227 } 228 break; 229 } 230 case (nlohmann::json::value_t::string): 231 { 232 if (array) 233 { 234 addArrayToDbus<std::string>(key, value, iface.get(), 235 permission, systemConfiguration, 236 path); 237 } 238 else 239 { 240 addProperty(key, value.get<std::string>(), iface.get(), 241 systemConfiguration, path, permission); 242 } 243 break; 244 } 245 default: 246 { 247 std::cerr << "Unexpected json type in system configuration " 248 << key << ": " << value.type_name() << "\n"; 249 break; 250 } 251 } 252 } 253 if (permission == sdbusplus::asio::PropertyPermission::readWrite) 254 { 255 createDeleteObjectMethod(jsonPointerPath, iface, objServer, 256 systemConfiguration); 257 } 258 tryIfaceInitialize(iface); 259 } 260 261 void createAddObjectMethod( 262 const std::string& jsonPointerPath, const std::string& path, 263 nlohmann::json& systemConfiguration, 264 sdbusplus::asio::object_server& objServer, const std::string& board) 265 { 266 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = createInterface( 267 objServer, path, "xyz.openbmc_project.AddObject", board); 268 269 iface->register_method( 270 "AddObject", 271 [&systemConfiguration, &objServer, 272 jsonPointerPath{std::string(jsonPointerPath)}, path{std::string(path)}, 273 board](const boost::container::flat_map<std::string, JsonVariantType>& 274 data) { 275 nlohmann::json::json_pointer ptr(jsonPointerPath); 276 nlohmann::json& base = systemConfiguration[ptr]; 277 auto findExposes = base.find("Exposes"); 278 279 if (findExposes == base.end()) 280 { 281 throw std::invalid_argument("Entity must have children."); 282 } 283 284 // this will throw invalid-argument to sdbusplus if invalid json 285 nlohmann::json newData{}; 286 for (const auto& item : data) 287 { 288 nlohmann::json& newJson = newData[item.first]; 289 std::visit( 290 [&newJson](auto&& val) { 291 newJson = std::forward<decltype(val)>(val); 292 }, 293 item.second); 294 } 295 296 auto findName = newData.find("Name"); 297 auto findType = newData.find("Type"); 298 if (findName == newData.end() || findType == newData.end()) 299 { 300 throw std::invalid_argument("AddObject missing Name or Type"); 301 } 302 const std::string* type = findType->get_ptr<const std::string*>(); 303 const std::string* name = findName->get_ptr<const std::string*>(); 304 if (type == nullptr || name == nullptr) 305 { 306 throw std::invalid_argument("Type and Name must be a string."); 307 } 308 309 bool foundNull = false; 310 size_t lastIndex = 0; 311 // we add in the "exposes" 312 for (const auto& expose : *findExposes) 313 { 314 if (expose.is_null()) 315 { 316 foundNull = true; 317 continue; 318 } 319 320 if (expose["Name"] == *name && expose["Type"] == *type) 321 { 322 throw std::invalid_argument( 323 "Field already in JSON, not adding"); 324 } 325 326 if (foundNull) 327 { 328 continue; 329 } 330 331 lastIndex++; 332 } 333 334 std::ifstream schemaFile( 335 std::string(configuration::schemaDirectory) + "/" + 336 boost::to_lower_copy(*type) + ".json"); 337 // todo(james) we might want to also make a list of 'can add' 338 // interfaces but for now I think the assumption if there is a 339 // schema avaliable that it is allowed to update is fine 340 if (!schemaFile.good()) 341 { 342 throw std::invalid_argument( 343 "No schema avaliable, cannot validate."); 344 } 345 nlohmann::json schema = 346 nlohmann::json::parse(schemaFile, nullptr, false, true); 347 if (schema.is_discarded()) 348 { 349 std::cerr << "Schema not legal" << *type << ".json\n"; 350 throw DBusInternalError(); 351 } 352 if (!configuration::validateJson(schema, newData)) 353 { 354 throw std::invalid_argument("Data does not match schema"); 355 } 356 if (foundNull) 357 { 358 findExposes->at(lastIndex) = newData; 359 } 360 else 361 { 362 findExposes->push_back(newData); 363 } 364 if (!configuration::writeJsonFiles(systemConfiguration)) 365 { 366 std::cerr << "Error writing json files\n"; 367 throw DBusInternalError(); 368 } 369 std::string dbusName = *name; 370 371 std::regex_replace(dbusName.begin(), dbusName.begin(), 372 dbusName.end(), illegalDbusMemberRegex, "_"); 373 374 std::shared_ptr<sdbusplus::asio::dbus_interface> interface = 375 createInterface(objServer, path + "/" + dbusName, 376 "xyz.openbmc_project.Configuration." + *type, 377 board, true); 378 // permission is read-write, as since we just created it, must be 379 // runtime modifiable 380 populateInterfaceFromJson( 381 systemConfiguration, 382 jsonPointerPath + "/Exposes/" + std::to_string(lastIndex), 383 interface, newData, objServer, 384 sdbusplus::asio::PropertyPermission::readWrite); 385 }); 386 tryIfaceInitialize(iface); 387 } 388 389 std::vector<std::weak_ptr<sdbusplus::asio::dbus_interface>>& 390 getDeviceInterfaces(const nlohmann::json& device) 391 { 392 return inventory[device["Name"].get<std::string>()]; 393 } 394 395 } // namespace dbus_interface 396