1 // SPDX-License-Identifier: Apache-2.0 2 3 /**@file functions.cpp*/ 4 5 #include "functions.hpp" 6 7 #include <sdbusplus/bus.hpp> 8 #include <sdbusplus/bus/match.hpp> 9 #include <sdbusplus/exception.hpp> 10 #include <sdbusplus/message.hpp> 11 #include <sdeventplus/event.hpp> 12 13 #include <filesystem> 14 #include <functional> 15 #include <iostream> 16 #include <map> 17 #include <memory> 18 #include <string> 19 #include <variant> 20 #include <vector> 21 22 namespace functions 23 { 24 namespace process_hostfirmware 25 { 26 27 /** 28 * @brief Issue callbacks safely 29 * 30 * std::function can be empty, so this wrapper method checks for that prior to 31 * calling it to avoid std::bad_function_call 32 * 33 * @tparam Sig the types of the std::function arguments 34 * @tparam Args the deduced argument types 35 * @param[in] callback the callback being wrapped 36 * @param[in] args the callback arguments 37 */ 38 template <typename... Sig, typename... Args> 39 void makeCallback(const std::function<void(Sig...)>& callback, Args&&... args) 40 { 41 if (callback) 42 { 43 callback(std::forward<Args>(args)...); 44 } 45 } 46 47 /** 48 * @brief Get file extensions for IBMCompatibleSystem 49 * 50 * IBM host firmware can be deployed as blobs (files) in a filesystem. Host 51 * firmware blobs for different values of 52 * xyz.openbmc_project.Configuration.IBMCompatibleSystem are packaged with 53 * different filename extensions. getExtensionsForIbmCompatibleSystem 54 * maintains the mapping from a given value of 55 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to an array of 56 * filename extensions. 57 * 58 * If a mapping is found getExtensionsForIbmCompatibleSystem returns true and 59 * the extensions parameter is reset with the map entry. If no mapping is 60 * found getExtensionsForIbmCompatibleSystem returns false and extensions is 61 * unmodified. 62 * 63 * @param[in] extensionMap a map of 64 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob 65 * file extensions. 66 * @param[in] ibmCompatibleSystem The names property of an instance of 67 * xyz.openbmc_project.Configuration.IBMCompatibleSystem 68 * @param[out] extentions the host firmware blob file extensions 69 * @return true if an entry was found, otherwise false 70 */ 71 bool getExtensionsForIbmCompatibleSystem( 72 const std::map<std::string, std::vector<std::string>>& extensionMap, 73 const std::vector<std::string>& ibmCompatibleSystem, 74 std::vector<std::string>& extensions) 75 { 76 for (const auto& system : ibmCompatibleSystem) 77 { 78 auto extensionMapIterator = extensionMap.find(system); 79 if (extensionMapIterator != extensionMap.end()) 80 { 81 extensions = extensionMapIterator->second; 82 return true; 83 } 84 } 85 86 return false; 87 } 88 89 /** 90 * @brief Write host firmware well-known name 91 * 92 * A wrapper around std::filesystem::create_symlink that avoids EEXIST by 93 * deleting any pre-existing file. 94 * 95 * @param[in] linkTarget The link target argument to 96 * std::filesystem::create_symlink 97 * @param[in] linkPath The link path argument to std::filesystem::create_symlink 98 * @param[in] errorCallback A callback made in the event of filesystem errors. 99 */ 100 void writeLink(const std::filesystem::path& linkTarget, 101 const std::filesystem::path& linkPath, 102 const ErrorCallbackType& errorCallback) 103 { 104 std::error_code ec; 105 106 // remove files with the same name as the symlink to be created, 107 // otherwise symlink will fail with EEXIST. 108 if (!std::filesystem::remove(linkPath, ec)) 109 { 110 if (ec) 111 { 112 makeCallback(errorCallback, linkPath, ec); 113 return; 114 } 115 } 116 117 std::filesystem::create_symlink(linkTarget, linkPath, ec); 118 if (ec) 119 { 120 makeCallback(errorCallback, linkPath, ec); 121 return; 122 } 123 } 124 125 /** 126 * @brief Find host firmware blob files that need well-known names 127 * 128 * The IBM host firmware runtime looks for data and/or additional code while 129 * bootstraping in files with well-known names. findLinks uses the provided 130 * extensions argument to find host firmware blob files that require a 131 * well-known name. When a blob is found, issue the provided callback 132 * (typically a function that will write a symlink). 133 * 134 * @param[in] hostFirmwareDirectory The directory in which findLinks should 135 * look for host firmware blob files that need well-known names. 136 * @param[in] extentions The extensions of the firmware blob files denote a 137 * host firmware blob file requires a well-known name. 138 * @param[in] errorCallback A callback made in the event of filesystem errors. 139 * @param[in] linkCallback A callback made when host firmware blob files 140 * needing a well known name are found. 141 */ 142 void findLinks(const std::filesystem::path& hostFirmwareDirectory, 143 const std::vector<std::string>& extensions, 144 const ErrorCallbackType& errorCallback, 145 const LinkCallbackType& linkCallback) 146 { 147 std::error_code ec; 148 std::filesystem::directory_iterator directoryIterator(hostFirmwareDirectory, 149 ec); 150 if (ec) 151 { 152 makeCallback(errorCallback, hostFirmwareDirectory, ec); 153 return; 154 } 155 156 // Create a symlink from HBB to the corresponding LID file if it exists 157 static const auto hbbLid = "81e0065a.lid"; 158 auto hbbLidPath = hostFirmwareDirectory / hbbLid; 159 if (std::filesystem::exists(hbbLidPath)) 160 { 161 static const auto hbbName = "HBB"; 162 auto hbbLinkPath = hostFirmwareDirectory / hbbName; 163 makeCallback(linkCallback, hbbLid, hbbLinkPath, errorCallback); 164 } 165 166 for (; directoryIterator != std::filesystem::end(directoryIterator); 167 directoryIterator.increment(ec)) 168 { 169 const auto& file = directoryIterator->path(); 170 if (ec) 171 { 172 makeCallback(errorCallback, file, ec); 173 // quit here if the increment call failed otherwise the loop may 174 // never finish 175 break; 176 } 177 178 if (std::find(extensions.begin(), extensions.end(), file.extension()) == 179 extensions.end()) 180 { 181 // this file doesn't have an extension or doesn't match any of the 182 // provided extensions. 183 continue; 184 } 185 186 auto linkPath(file.parent_path().append( 187 static_cast<const std::string&>(file.stem()))); 188 189 makeCallback(linkCallback, file.filename(), linkPath, errorCallback); 190 } 191 } 192 193 /** 194 * @brief Set the bios attribute table with details of the host firmware data 195 * for this system. 196 */ 197 void setBiosAttr() 198 {} 199 200 /** 201 * @brief Make callbacks on 202 * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances. 203 * 204 * Look for an instance of 205 * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided 206 * argument and if found, issue the provided callback. 207 * 208 * @param[in] interfacesAndProperties the interfaces in which to look for an 209 * instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem 210 * @param[in] callback the user callback to make if 211 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in 212 * interfacesAndProperties 213 * @return true if interfacesAndProperties contained an instance of 214 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise 215 */ 216 bool maybeCall(const std::map<std::string, 217 std::map<std::string, 218 std::variant<std::vector<std::string>>>>& 219 interfacesAndProperties, 220 const MaybeCallCallbackType& callback) 221 { 222 using namespace std::string_literals; 223 224 static const auto interfaceName = 225 "xyz.openbmc_project.Configuration.IBMCompatibleSystem"s; 226 auto interfaceIterator = interfacesAndProperties.find(interfaceName); 227 if (interfaceIterator == interfacesAndProperties.cend()) 228 { 229 // IBMCompatibleSystem interface not found, so instruct the caller to 230 // keep waiting or try again later. 231 return false; 232 } 233 auto propertyIterator = interfaceIterator->second.find("Names"s); 234 if (propertyIterator == interfaceIterator->second.cend()) 235 { 236 // The interface exists but the property doesn't. This is a bug in the 237 // IBMCompatibleSystem implementation. The caller should not try 238 // again. 239 std::cerr << "Names property not implemented on " << interfaceName 240 << "\n"; 241 return true; 242 } 243 244 const auto& ibmCompatibleSystem = 245 std::get<std::vector<std::string>>(propertyIterator->second); 246 if (callback) 247 { 248 callback(ibmCompatibleSystem); 249 } 250 251 // IBMCompatibleSystem found and callback issued. 252 return true; 253 } 254 255 /** 256 * @brief Make callbacks on 257 * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances. 258 * 259 * Look for an instance of 260 * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided 261 * argument and if found, issue the provided callback. 262 * 263 * @param[in] message the DBus message in which to look for an instance of 264 * xyz.openbmc_project.Configuration.IBMCompatibleSystem 265 * @param[in] callback the user callback to make if 266 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in 267 * message 268 * @return true if message contained an instance of 269 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise 270 */ 271 bool maybeCallMessage(sdbusplus::message::message& message, 272 const MaybeCallCallbackType& callback) 273 { 274 std::map<std::string, 275 std::map<std::string, std::variant<std::vector<std::string>>>> 276 interfacesAndProperties; 277 sdbusplus::message::object_path _; 278 message.read(_, interfacesAndProperties); 279 return maybeCall(interfacesAndProperties, callback); 280 } 281 282 /** 283 * @brief Determine system support for host firmware well-known names. 284 * 285 * Using the provided extensionMap and 286 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if 287 * well-known names for host firmare blob files are necessary and if so, create 288 * them. 289 * 290 * @param[in] extensionMap a map of 291 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob 292 * file extensions. 293 * @param[in] hostFirmwareDirectory The directory in which findLinks should 294 * look for host firmware blob files that need well-known names. 295 * @param[in] ibmCompatibleSystem The names property of an instance of 296 * xyz.openbmc_project.Configuration.IBMCompatibleSystem 297 * @param[in] errorCallback A callback made in the event of filesystem errors. 298 */ 299 void maybeMakeLinks( 300 const std::map<std::string, std::vector<std::string>>& extensionMap, 301 const std::filesystem::path& hostFirmwareDirectory, 302 const std::vector<std::string>& ibmCompatibleSystem, 303 const ErrorCallbackType& errorCallback) 304 { 305 std::vector<std::string> extensions; 306 if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem, 307 extensions)) 308 { 309 findLinks(hostFirmwareDirectory, extensions, errorCallback, writeLink); 310 } 311 } 312 313 /** 314 * @brief Determine system support for updating the bios attribute table. 315 * 316 * Using the provided extensionMap and 317 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if the bios 318 * attribute table needs to be updated. 319 * 320 * @param[in] extensionMap a map of 321 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob 322 * file extensions. 323 * @param[in] ibmCompatibleSystem The names property of an instance of 324 * xyz.openbmc_project.Configuration.IBMCompatibleSystem 325 */ 326 void maybeSetBiosAttr( 327 const std::map<std::string, std::vector<std::string>>& extensionMap, 328 const std::vector<std::string>& ibmCompatibleSystem) 329 { 330 std::vector<std::string> extensions; 331 if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem, 332 extensions)) 333 { 334 setBiosAttr(); 335 } 336 } 337 338 /** 339 * @brief process host firmware 340 * 341 * Allocate a callback context and register for DBus.ObjectManager Interfaces 342 * added signals from entity manager. 343 * 344 * Check the current entity manager object tree for a 345 * xyz.openbmc_project.Configuration.IBMCompatibleSystem instance (entity 346 * manager will be dbus activated if it is not running). If one is found, 347 * determine if symlinks need to be created and create them. Instruct the 348 * program event loop to exit. 349 * 350 * If no instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is 351 * found return the callback context to main, where the program will sleep 352 * until the callback is invoked one or more times and instructs the program 353 * event loop to exit when 354 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is added. 355 * 356 * @param[in] bus a DBus client connection 357 * @param[in] extensionMap a map of 358 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob 359 * file extensions. 360 * @param[in] hostFirmwareDirectory The directory in which processHostFirmware 361 * should look for blob files. 362 * @param[in] errorCallback A callback made in the event of filesystem errors. 363 * @param[in] loop a program event loop 364 * @return nullptr if an instance of 365 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found, otherwise a 366 * pointer to an sdbusplus match object. 367 */ 368 std::shared_ptr<void> processHostFirmware( 369 sdbusplus::bus::bus& bus, 370 std::map<std::string, std::vector<std::string>> extensionMap, 371 std::filesystem::path hostFirmwareDirectory, 372 ErrorCallbackType errorCallback, sdeventplus::Event& loop) 373 { 374 // ownership of extensionMap, hostFirmwareDirectory and errorCallback can't 375 // be transfered to the match callback because they are needed in the non 376 // async part of this function below, so they need to be moved to the heap. 377 auto pExtensionMap = 378 std::make_shared<decltype(extensionMap)>(std::move(extensionMap)); 379 auto pHostFirmwareDirectory = 380 std::make_shared<decltype(hostFirmwareDirectory)>( 381 std::move(hostFirmwareDirectory)); 382 auto pErrorCallback = 383 std::make_shared<decltype(errorCallback)>(std::move(errorCallback)); 384 385 // register for a callback in case the IBMCompatibleSystem interface has 386 // not yet been published by entity manager. 387 auto interfacesAddedMatch = std::make_shared<sdbusplus::bus::match::match>( 388 bus, 389 sdbusplus::bus::match::rules::interfacesAdded() + 390 sdbusplus::bus::match::rules::sender( 391 "xyz.openbmc_project.EntityManager"), 392 [pExtensionMap, pHostFirmwareDirectory, pErrorCallback, 393 &loop](auto& message) { 394 // bind the extension map, host firmware directory, and error 395 // callback to the maybeMakeLinks function. 396 auto maybeMakeLinksWithArgsBound = 397 std::bind(maybeMakeLinks, std::cref(*pExtensionMap), 398 std::cref(*pHostFirmwareDirectory), 399 std::placeholders::_1, std::cref(*pErrorCallback)); 400 401 // if the InterfacesAdded message contains an an instance of 402 // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to 403 // see if links are necessary on this system and if so, create 404 // them. 405 if (maybeCallMessage(message, maybeMakeLinksWithArgsBound)) 406 { 407 // The IBMCompatibleSystem interface was found and the links 408 // were created if applicable. Instruct the event loop / 409 // subcommand to exit. 410 loop.exit(0); 411 } 412 }); 413 414 // now that we'll get a callback in the event of an InterfacesAdded signal 415 // (potentially containing 416 // xyz.openbmc_project.Configuration.IBMCompatibleSystem), activate entity 417 // manager if it isn't running and enumerate its objects 418 auto getManagedObjects = bus.new_method_call( 419 "xyz.openbmc_project.EntityManager", "/", 420 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 421 std::map<std::string, 422 std::map<std::string, std::variant<std::vector<std::string>>>> 423 interfacesAndProperties; 424 std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)> 425 objects; 426 try 427 { 428 auto reply = bus.call(getManagedObjects); 429 reply.read(objects); 430 } 431 catch (const sdbusplus::exception::SdBusError& e) 432 { 433 // Error querying the EntityManager interface. Return the match to have 434 // the callback run if/when the interface appears in D-Bus. 435 return interfacesAddedMatch; 436 } 437 438 // bind the extension map, host firmware directory, and error callback to 439 // the maybeMakeLinks function. 440 auto maybeMakeLinksWithArgsBound = 441 std::bind(maybeMakeLinks, std::cref(*pExtensionMap), 442 std::cref(*pHostFirmwareDirectory), std::placeholders::_1, 443 std::cref(*pErrorCallback)); 444 445 for (const auto& pair : objects) 446 { 447 std::tie(std::ignore, interfacesAndProperties) = pair; 448 // if interfacesAndProperties contains an an instance of 449 // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to see 450 // if links are necessary on this system and if so, create them 451 if (maybeCall(interfacesAndProperties, maybeMakeLinksWithArgsBound)) 452 { 453 // The IBMCompatibleSystem interface is already on the bus and the 454 // links were created if applicable. Instruct the event loop to 455 // exit. 456 loop.exit(0); 457 // The match object isn't needed anymore, so destroy it on return. 458 return nullptr; 459 } 460 } 461 462 // The IBMCompatibleSystem interface has not yet been published. Move 463 // ownership of the match callback to the caller. 464 return interfacesAddedMatch; 465 } 466 467 /** 468 * @brief Update the Bios Attribute Table 469 * 470 * If an instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is 471 * found, update the Bios Attribute Table with the appropriate host firmware 472 * data. 473 * 474 * @param[in] bus - D-Bus client connection. 475 * @param[in] extensionMap - Map of IBMCompatibleSystem names and host firmware 476 * file extensions. 477 * @param[in] loop - Program event loop. 478 * @return nullptr 479 */ 480 std::shared_ptr<void> updateBiosAttrTable( 481 sdbusplus::bus::bus& bus, 482 std::map<std::string, std::vector<std::string>> extensionMap, 483 sdeventplus::Event& loop) 484 { 485 auto pExtensionMap = 486 std::make_shared<decltype(extensionMap)>(std::move(extensionMap)); 487 488 auto getManagedObjects = bus.new_method_call( 489 "xyz.openbmc_project.EntityManager", "/", 490 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 491 std::map<std::string, 492 std::map<std::string, std::variant<std::vector<std::string>>>> 493 interfacesAndProperties; 494 std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)> 495 objects; 496 try 497 { 498 auto reply = bus.call(getManagedObjects); 499 reply.read(objects); 500 } 501 catch (const sdbusplus::exception::SdBusError& e) 502 {} 503 504 auto maybeSetAttrWithArgsBound = std::bind( 505 maybeSetBiosAttr, std::cref(*pExtensionMap), std::placeholders::_1); 506 507 for (const auto& pair : objects) 508 { 509 std::tie(std::ignore, interfacesAndProperties) = pair; 510 if (maybeCall(interfacesAndProperties, maybeSetAttrWithArgsBound)) 511 { 512 break; 513 } 514 } 515 516 loop.exit(0); 517 return nullptr; 518 } 519 520 } // namespace process_hostfirmware 521 } // namespace functions 522