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 Set the bios attribute table with details of the host firmware data
195  * for this system.
196  */
197 void setBiosAttr()
198 {}
199 
200 /**
201  * @brief Make callbacks on
202  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
203  *
204  * Look for an instance of
205  * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
206  * argument and if found, issue the provided callback.
207  *
208  * @param[in] interfacesAndProperties the interfaces in which to look for an
209  * instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem
210  * @param[in] callback the user callback to make if
211  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
212  * interfacesAndProperties
213  * @return true if interfacesAndProperties contained an instance of
214  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
215  */
216 bool maybeCall(const std::map<std::string,
217                               std::map<std::string,
218                                        std::variant<std::vector<std::string>>>>&
219                    interfacesAndProperties,
220                const MaybeCallCallbackType& callback)
221 {
222     using namespace std::string_literals;
223 
224     static const auto interfaceName =
225         "xyz.openbmc_project.Configuration.IBMCompatibleSystem"s;
226     auto interfaceIterator = interfacesAndProperties.find(interfaceName);
227     if (interfaceIterator == interfacesAndProperties.cend())
228     {
229         // IBMCompatibleSystem interface not found, so instruct the caller to
230         // keep waiting or try again later.
231         return false;
232     }
233     auto propertyIterator = interfaceIterator->second.find("Names"s);
234     if (propertyIterator == interfaceIterator->second.cend())
235     {
236         // The interface exists but the property doesn't.  This is a bug in the
237         // IBMCompatibleSystem implementation.  The caller should not try
238         // again.
239         std::cerr << "Names property not implemented on " << interfaceName
240                   << "\n";
241         return true;
242     }
243 
244     const auto& ibmCompatibleSystem =
245         std::get<std::vector<std::string>>(propertyIterator->second);
246     if (callback)
247     {
248         callback(ibmCompatibleSystem);
249     }
250 
251     // IBMCompatibleSystem found and callback issued.
252     return true;
253 }
254 
255 /**
256  * @brief Make callbacks on
257  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
258  *
259  * Look for an instance of
260  * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
261  * argument and if found, issue the provided callback.
262  *
263  * @param[in] message the DBus message in which to look for an instance of
264  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
265  * @param[in] callback the user callback to make if
266  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
267  * message
268  * @return true if message contained an instance of
269  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
270  */
271 bool maybeCallMessage(sdbusplus::message::message& message,
272                       const MaybeCallCallbackType& callback)
273 {
274     std::map<std::string,
275              std::map<std::string, std::variant<std::vector<std::string>>>>
276         interfacesAndProperties;
277     sdbusplus::message::object_path _;
278     message.read(_, interfacesAndProperties);
279     return maybeCall(interfacesAndProperties, callback);
280 }
281 
282 /**
283  * @brief Determine system support for host firmware well-known names.
284  *
285  * Using the provided extensionMap and
286  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if
287  * well-known names for host firmare blob files are necessary and if so, create
288  * them.
289  *
290  * @param[in] extensionMap a map of
291  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
292  * file extensions.
293  * @param[in] hostFirmwareDirectory The directory in which findLinks should
294  * look for host firmware blob files that need well-known names.
295  * @param[in] ibmCompatibleSystem The names property of an instance of
296  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
297  * @param[in] errorCallback A callback made in the event of filesystem errors.
298  */
299 void maybeMakeLinks(
300     const std::map<std::string, std::vector<std::string>>& extensionMap,
301     const std::filesystem::path& hostFirmwareDirectory,
302     const std::vector<std::string>& ibmCompatibleSystem,
303     const ErrorCallbackType& errorCallback)
304 {
305     std::vector<std::string> extensions;
306     if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
307                                             extensions))
308     {
309         findLinks(hostFirmwareDirectory, extensions, errorCallback, writeLink);
310     }
311 }
312 
313 /**
314  * @brief Determine system support for updating the bios attribute table.
315  *
316  * Using the provided extensionMap and
317  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if the bios
318  * attribute table needs to be updated.
319  *
320  * @param[in] extensionMap a map of
321  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
322  * file extensions.
323  * @param[in] ibmCompatibleSystem The names property of an instance of
324  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
325  */
326 void maybeSetBiosAttr(
327     const std::map<std::string, std::vector<std::string>>& extensionMap,
328     const std::vector<std::string>& ibmCompatibleSystem)
329 {
330     std::vector<std::string> extensions;
331     if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
332                                             extensions))
333     {
334         setBiosAttr();
335     }
336 }
337 
338 /**
339  * @brief process host firmware
340  *
341  * Allocate a callback context and register for DBus.ObjectManager Interfaces
342  * added signals from entity manager.
343  *
344  * Check the current entity manager object tree for a
345  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instance (entity
346  * manager will be dbus activated if it is not running).  If one is found,
347  * determine if symlinks need to be created and create them.  Instruct the
348  * program event loop to exit.
349  *
350  * If no instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
351  * found return the callback context to main, where the program will sleep
352  * until the callback is invoked one or more times and instructs the program
353  * event loop to exit when
354  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is added.
355  *
356  * @param[in] bus a DBus client connection
357  * @param[in] extensionMap a map of
358  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
359  * file extensions.
360  * @param[in] hostFirmwareDirectory The directory in which processHostFirmware
361  * should look for blob files.
362  * @param[in] errorCallback A callback made in the event of filesystem errors.
363  * @param[in] loop a program event loop
364  * @return nullptr if an instance of
365  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found, otherwise a
366  * pointer to an sdbusplus match object.
367  */
368 std::shared_ptr<void> processHostFirmware(
369     sdbusplus::bus::bus& bus,
370     std::map<std::string, std::vector<std::string>> extensionMap,
371     std::filesystem::path hostFirmwareDirectory,
372     ErrorCallbackType errorCallback, sdeventplus::Event& loop)
373 {
374     // ownership of extensionMap, hostFirmwareDirectory and errorCallback can't
375     // be transfered to the match callback because they are needed in the non
376     // async part of this function below, so they need to be moved to the heap.
377     auto pExtensionMap =
378         std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
379     auto pHostFirmwareDirectory =
380         std::make_shared<decltype(hostFirmwareDirectory)>(
381             std::move(hostFirmwareDirectory));
382     auto pErrorCallback =
383         std::make_shared<decltype(errorCallback)>(std::move(errorCallback));
384 
385     // register for a callback in case the IBMCompatibleSystem interface has
386     // not yet been published by entity manager.
387     auto interfacesAddedMatch = std::make_shared<sdbusplus::bus::match::match>(
388         bus,
389         sdbusplus::bus::match::rules::interfacesAdded() +
390             sdbusplus::bus::match::rules::sender(
391                 "xyz.openbmc_project.EntityManager"),
392         [pExtensionMap, pHostFirmwareDirectory, pErrorCallback,
393          &loop](auto& message) {
394             // bind the extension map, host firmware directory, and error
395             // callback to the maybeMakeLinks function.
396             auto maybeMakeLinksWithArgsBound =
397                 std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
398                           std::cref(*pHostFirmwareDirectory),
399                           std::placeholders::_1, std::cref(*pErrorCallback));
400 
401             // if the InterfacesAdded message contains an an instance of
402             // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to
403             // see if links are necessary on this system and if so, create
404             // them.
405             if (maybeCallMessage(message, maybeMakeLinksWithArgsBound))
406             {
407                 // The IBMCompatibleSystem interface was found and the links
408                 // were created if applicable.  Instruct the event loop /
409                 // subcommand to exit.
410                 loop.exit(0);
411             }
412         });
413 
414     // now that we'll get a callback in the event of an InterfacesAdded signal
415     // (potentially containing
416     // xyz.openbmc_project.Configuration.IBMCompatibleSystem), activate entity
417     // manager if it isn't running and enumerate its objects
418     auto getManagedObjects = bus.new_method_call(
419         "xyz.openbmc_project.EntityManager", "/",
420         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
421     std::map<std::string,
422              std::map<std::string, std::variant<std::vector<std::string>>>>
423         interfacesAndProperties;
424     std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)>
425         objects;
426     try
427     {
428         auto reply = bus.call(getManagedObjects);
429         reply.read(objects);
430     }
431     catch (const sdbusplus::exception::SdBusError& e)
432     {
433         // Error querying the EntityManager interface. Return the match to have
434         // the callback run if/when the interface appears in D-Bus.
435         return interfacesAddedMatch;
436     }
437 
438     // bind the extension map, host firmware directory, and error callback to
439     // the maybeMakeLinks function.
440     auto maybeMakeLinksWithArgsBound =
441         std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
442                   std::cref(*pHostFirmwareDirectory), std::placeholders::_1,
443                   std::cref(*pErrorCallback));
444 
445     for (const auto& pair : objects)
446     {
447         std::tie(std::ignore, interfacesAndProperties) = pair;
448         // if interfacesAndProperties contains an an instance of
449         // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to see
450         // if links are necessary on this system and if so, create them
451         if (maybeCall(interfacesAndProperties, maybeMakeLinksWithArgsBound))
452         {
453             // The IBMCompatibleSystem interface is already on the bus and the
454             // links were created if applicable.  Instruct the event loop to
455             // exit.
456             loop.exit(0);
457             // The match object isn't needed anymore, so destroy it on return.
458             return nullptr;
459         }
460     }
461 
462     // The IBMCompatibleSystem interface has not yet been published.  Move
463     // ownership of the match callback to the caller.
464     return interfacesAddedMatch;
465 }
466 
467 /**
468  * @brief Update the Bios Attribute Table
469  *
470  * If an instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
471  * found, update the Bios Attribute Table with the appropriate host firmware
472  * data.
473  *
474  * @param[in] bus - D-Bus client connection.
475  * @param[in] extensionMap - Map of IBMCompatibleSystem names and host firmware
476  *                           file extensions.
477  * @param[in] loop - Program event loop.
478  * @return nullptr
479  */
480 std::shared_ptr<void> updateBiosAttrTable(
481     sdbusplus::bus::bus& bus,
482     std::map<std::string, std::vector<std::string>> extensionMap,
483     sdeventplus::Event& loop)
484 {
485     auto pExtensionMap =
486         std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
487 
488     auto getManagedObjects = bus.new_method_call(
489         "xyz.openbmc_project.EntityManager", "/",
490         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
491     std::map<std::string,
492              std::map<std::string, std::variant<std::vector<std::string>>>>
493         interfacesAndProperties;
494     std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)>
495         objects;
496     try
497     {
498         auto reply = bus.call(getManagedObjects);
499         reply.read(objects);
500     }
501     catch (const sdbusplus::exception::SdBusError& e)
502     {}
503 
504     auto maybeSetAttrWithArgsBound = std::bind(
505         maybeSetBiosAttr, std::cref(*pExtensionMap), std::placeholders::_1);
506 
507     for (const auto& pair : objects)
508     {
509         std::tie(std::ignore, interfacesAndProperties) = pair;
510         if (maybeCall(interfacesAndProperties, maybeSetAttrWithArgsBound))
511         {
512             break;
513         }
514     }
515 
516     loop.exit(0);
517     return nullptr;
518 }
519 
520 } // namespace process_hostfirmware
521 } // namespace functions
522