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/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 
21 namespace functions
22 {
23 namespace 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  */
37 template <typename... Sig, typename... Args>
38 void 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  */
70 bool 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  */
99 void 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  */
141 void 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     // 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 
165     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  */
208 bool 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  */
263 bool 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  */
291 void 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  */
335 std::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
426