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/exception.hpp>
10 #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 
22 namespace functions
23 {
24 namespace 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  */
38 template <typename... Sig, typename... Args>
39 void 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  */
71 bool 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  */
100 void 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  */
142 void 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 
156     // 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 
166     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  */
209 bool 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  */
264 bool 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  */
292 void 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  */
336 std::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");
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     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     }
405 
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
436