blob: cad510a41b77653e97b8f4e9ffc56e3dca1177cd [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/**
194 * @brief Make callbacks on
195 * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
196 *
197 * Look for an instance of
198 * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
199 * argument and if found, issue the provided callback.
200 *
201 * @param[in] interfacesAndProperties the interfaces in which to look for an
202 * instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem
203 * @param[in] callback the user callback to make if
204 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
205 * interfacesAndProperties
206 * @return true if interfacesAndProperties contained an instance of
207 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
208 */
209bool maybeCall(const std::map<std::string,
210 std::map<std::string,
211 std::variant<std::vector<std::string>>>>&
212 interfacesAndProperties,
213 const MaybeCallCallbackType& callback)
214{
215 using namespace std::string_literals;
216
217 static const auto interfaceName =
218 "xyz.openbmc_project.Configuration.IBMCompatibleSystem"s;
219 auto interfaceIterator = interfacesAndProperties.find(interfaceName);
220 if (interfaceIterator == interfacesAndProperties.cend())
221 {
222 // IBMCompatibleSystem interface not found, so instruct the caller to
223 // keep waiting or try again later.
224 return false;
225 }
226 auto propertyIterator = interfaceIterator->second.find("Names"s);
227 if (propertyIterator == interfaceIterator->second.cend())
228 {
229 // The interface exists but the property doesn't. This is a bug in the
230 // IBMCompatibleSystem implementation. The caller should not try
231 // again.
232 std::cerr << "Names property not implemented on " << interfaceName
233 << "\n";
234 return true;
235 }
236
237 const auto& ibmCompatibleSystem =
238 std::get<std::vector<std::string>>(propertyIterator->second);
239 if (callback)
240 {
241 callback(ibmCompatibleSystem);
242 }
243
244 // IBMCompatibleSystem found and callback issued.
245 return true;
246}
247
248/**
249 * @brief Make callbacks on
250 * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
251 *
252 * Look for an instance of
253 * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
254 * argument and if found, issue the provided callback.
255 *
256 * @param[in] message the DBus message in which to look for an instance of
257 * xyz.openbmc_project.Configuration.IBMCompatibleSystem
258 * @param[in] callback the user callback to make if
259 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
260 * message
261 * @return true if message contained an instance of
262 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
263 */
264bool maybeCallMessage(sdbusplus::message::message& message,
265 const MaybeCallCallbackType& callback)
266{
267 std::map<std::string,
268 std::map<std::string, std::variant<std::vector<std::string>>>>
269 interfacesAndProperties;
270 sdbusplus::message::object_path _;
271 message.read(_, interfacesAndProperties);
272 return maybeCall(interfacesAndProperties, callback);
273}
274
275/**
276 * @brief Determine system support for host firmware well-known names.
277 *
278 * Using the provided extensionMap and
279 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if
280 * well-known names for host firmare blob files are necessary and if so, create
281 * them.
282 *
283 * @param[in] extensionMap a map of
284 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
285 * file extensions.
286 * @param[in] hostFirmwareDirectory The directory in which findLinks should
287 * look for host firmware blob files that need well-known names.
288 * @param[in] ibmCompatibleSystem The names property of an instance of
289 * xyz.openbmc_project.Configuration.IBMCompatibleSystem
290 * @param[in] errorCallback A callback made in the event of filesystem errors.
291 */
292void maybeMakeLinks(
293 const std::map<std::string, std::vector<std::string>>& extensionMap,
294 const std::filesystem::path& hostFirmwareDirectory,
295 const std::vector<std::string>& ibmCompatibleSystem,
296 const ErrorCallbackType& errorCallback)
297{
298 std::vector<std::string> extensions;
299 if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
300 extensions))
301 {
302 findLinks(hostFirmwareDirectory, extensions, errorCallback, writeLink);
303 }
304}
305
306/**
307 * @brief process host firmware
308 *
309 * Allocate a callback context and register for DBus.ObjectManager Interfaces
310 * added signals from entity manager.
311 *
312 * Check the current entity manager object tree for a
313 * xyz.openbmc_project.Configuration.IBMCompatibleSystem instance (entity
314 * manager will be dbus activated if it is not running). If one is found,
315 * determine if symlinks need to be created and create them. Instruct the
316 * program event loop to exit.
317 *
318 * If no instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
319 * found return the callback context to main, where the program will sleep
320 * until the callback is invoked one or more times and instructs the program
321 * event loop to exit when
322 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is added.
323 *
324 * @param[in] bus a DBus client connection
325 * @param[in] extensionMap a map of
326 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
327 * file extensions.
328 * @param[in] hostFirmwareDirectory The directory in which processHostFirmware
329 * should look for blob files.
330 * @param[in] errorCallback A callback made in the event of filesystem errors.
331 * @param[in] loop a program event loop
332 * @return nullptr if an instance of
333 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found, otherwise a
334 * pointer to an sdbusplus match object.
335 */
336std::shared_ptr<void> processHostFirmware(
337 sdbusplus::bus::bus& bus,
338 std::map<std::string, std::vector<std::string>> extensionMap,
339 std::filesystem::path hostFirmwareDirectory,
340 ErrorCallbackType errorCallback, sdeventplus::Event& loop)
341{
342 // ownership of extensionMap, hostFirmwareDirectory and errorCallback can't
343 // be transfered to the match callback because they are needed in the non
344 // async part of this function below, so they need to be moved to the heap.
345 auto pExtensionMap =
346 std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
347 auto pHostFirmwareDirectory =
348 std::make_shared<decltype(hostFirmwareDirectory)>(
349 std::move(hostFirmwareDirectory));
350 auto pErrorCallback =
351 std::make_shared<decltype(errorCallback)>(std::move(errorCallback));
352
353 // register for a callback in case the IBMCompatibleSystem interface has
354 // not yet been published by entity manager.
355 auto interfacesAddedMatch = std::make_shared<sdbusplus::bus::match::match>(
356 bus,
357 sdbusplus::bus::match::rules::interfacesAdded() +
358 sdbusplus::bus::match::rules::sender(
359 "xyz.openbmc_project.EntityManager"),
360 [pExtensionMap, pHostFirmwareDirectory, pErrorCallback,
361 &loop](auto& message) {
362 // bind the extension map, host firmware directory, and error
363 // callback to the maybeMakeLinks function.
364 auto maybeMakeLinksWithArgsBound =
365 std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
366 std::cref(*pHostFirmwareDirectory),
367 std::placeholders::_1, std::cref(*pErrorCallback));
368
369 // if the InterfacesAdded message contains an an instance of
370 // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to
371 // see if links are necessary on this system and if so, create
372 // them.
373 if (maybeCallMessage(message, maybeMakeLinksWithArgsBound))
374 {
375 // The IBMCompatibleSystem interface was found and the links
376 // were created if applicable. Instruct the event loop /
377 // subcommand to exit.
378 loop.exit(0);
379 }
380 });
381
382 // now that we'll get a callback in the event of an InterfacesAdded signal
383 // (potentially containing
384 // xyz.openbmc_project.Configuration.IBMCompatibleSystem), activate entity
385 // manager if it isn't running and enumerate its objects
386 auto getManagedObjects = bus.new_method_call(
387 "xyz.openbmc_project.EntityManager", "/",
388 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
Brad Bishop099543e2020-11-09 15:37:58 -0500389 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;
Adriana Kobylakc79fa912021-06-22 15:37:50 +0000394 try
395 {
396 auto reply = bus.call(getManagedObjects);
397 reply.read(objects);
398 }
399 catch (const sdbusplus::exception::SdBusError& e)
400 {
401 // Error querying the EntityManager interface. Return the match to have
402 // the callback run if/when the interface appears in D-Bus.
403 return interfacesAddedMatch;
404 }
Brad Bishop099543e2020-11-09 15:37:58 -0500405
406 // bind the extension map, host firmware directory, and error callback to
407 // the maybeMakeLinks function.
408 auto maybeMakeLinksWithArgsBound =
409 std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
410 std::cref(*pHostFirmwareDirectory), std::placeholders::_1,
411 std::cref(*pErrorCallback));
412
413 for (const auto& pair : objects)
414 {
415 std::tie(std::ignore, interfacesAndProperties) = pair;
416 // if interfacesAndProperties contains an an instance of
417 // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to see
418 // if links are necessary on this system and if so, create them
419 if (maybeCall(interfacesAndProperties, maybeMakeLinksWithArgsBound))
420 {
421 // The IBMCompatibleSystem interface is already on the bus and the
422 // links were created if applicable. Instruct the event loop to
423 // exit.
424 loop.exit(0);
425 // The match object isn't needed anymore, so destroy it on return.
426 return nullptr;
427 }
428 }
429
430 // The IBMCompatibleSystem interface has not yet been published. Move
431 // ownership of the match callback to the caller.
432 return interfacesAddedMatch;
433}
434} // namespace process_hostfirmware
435} // namespace functions