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