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