blob: 40af65adf5a71af06d11b4c132ee90cfcae931fc [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>
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
21namespace functions
22{
23namespace 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 */
37template <typename... Sig, typename... Args>
38void 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 */
70bool 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 */
99void 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 */
141void 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
Adriana Kobylakfdc91fa2021-05-20 16:00:45 +0000155 // 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
Brad Bishop099543e2020-11-09 15:37:58 -0500165 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 */
208bool 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 */
263bool 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 */
291void 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 */
335std::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