blob: 365827065600a3cc0f1a79871894eee591fa5c82 [file] [log] [blame]
Brad Bishop099543e2020-11-09 15:37:58 -05001// 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>
Adriana Kobylakc79fa912021-06-22 15:37:50 +00009#include <sdbusplus/exception.hpp>
Brad Bishop099543e2020-11-09 15:37:58 -050010#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
22namespace functions
23{
24namespace 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 */
38template <typename... Sig, typename... Args>
39void 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 */
71bool 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 */
100void 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 */
142void 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
Adriana Kobylakfdc91fa2021-05-20 16:00:45 +0000156 // 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
Brad Bishop099543e2020-11-09 15:37:58 -0500166 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/**
Adriana Kobylak53a27392021-06-14 17:42:40 +0000194 * @brief Set the bios attribute table with details of the host firmware data
195 * for this system.
196 */
197void setBiosAttr()
198{}
199
200/**
Brad Bishop099543e2020-11-09 15:37:58 -0500201 * @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 */
216bool 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 */
271bool 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 */
299void 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/**
Adriana Kobylak53a27392021-06-14 17:42:40 +0000314 * @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 */
326void 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/**
Brad Bishop099543e2020-11-09 15:37:58 -0500339 * @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 */
368std::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");
Brad Bishop099543e2020-11-09 15:37:58 -0500421 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;
Adriana Kobylakc79fa912021-06-22 15:37:50 +0000426 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 }
Brad Bishop099543e2020-11-09 15:37:58 -0500437
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}
Adriana Kobylak53a27392021-06-14 17:42:40 +0000466
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 */
480std::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
Brad Bishop099543e2020-11-09 15:37:58 -0500520} // namespace process_hostfirmware
521} // namespace functions