1 // SPDX-License-Identifier: Apache-2.0
2 
3 /**@file functions.cpp*/
4 
5 #include "config.h"
6 
7 #include "functions.hpp"
8 
9 #include <phosphor-logging/log.hpp>
10 #include <sdbusplus/bus.hpp>
11 #include <sdbusplus/bus/match.hpp>
12 #include <sdbusplus/exception.hpp>
13 #include <sdbusplus/message.hpp>
14 #include <sdeventplus/event.hpp>
15 
16 #include <filesystem>
17 #include <functional>
18 #include <iostream>
19 #include <map>
20 #include <memory>
21 #include <string>
22 #include <variant>
23 #include <vector>
24 
25 namespace functions
26 {
27 namespace process_hostfirmware
28 {
29 
30 using namespace phosphor::logging;
31 
32 /**
33  * @brief Issue callbacks safely
34  *
35  * std::function can be empty, so this wrapper method checks for that prior to
36  * calling it to avoid std::bad_function_call
37  *
38  * @tparam Sig the types of the std::function arguments
39  * @tparam Args the deduced argument types
40  * @param[in] callback the callback being wrapped
41  * @param[in] args the callback arguments
42  */
43 template <typename... Sig, typename... Args>
44 void makeCallback(const std::function<void(Sig...)>& callback, Args&&... args)
45 {
46     if (callback)
47     {
48         callback(std::forward<Args>(args)...);
49     }
50 }
51 
52 /**
53  * @brief Get file extensions for IBMCompatibleSystem
54  *
55  * IBM host firmware can be deployed as blobs (files) in a filesystem.  Host
56  * firmware blobs for different values of
57  * xyz.openbmc_project.Configuration.IBMCompatibleSystem are packaged with
58  * different filename extensions.  getExtensionsForIbmCompatibleSystem
59  * maintains the mapping from a given value of
60  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to an array of
61  * filename extensions.
62  *
63  * If a mapping is found getExtensionsForIbmCompatibleSystem returns true and
64  * the extensions parameter is reset with the map entry.  If no mapping is
65  * found getExtensionsForIbmCompatibleSystem returns false and extensions is
66  * unmodified.
67  *
68  * @param[in] extensionMap a map of
69  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
70  * file extensions.
71  * @param[in] ibmCompatibleSystem The names property of an instance of
72  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
73  * @param[out] extentions the host firmware blob file extensions
74  * @return true if an entry was found, otherwise false
75  */
76 bool getExtensionsForIbmCompatibleSystem(
77     const std::map<std::string, std::vector<std::string>>& extensionMap,
78     const std::vector<std::string>& ibmCompatibleSystem,
79     std::vector<std::string>& extensions)
80 {
81     for (const auto& system : ibmCompatibleSystem)
82     {
83         auto extensionMapIterator = extensionMap.find(system);
84         if (extensionMapIterator != extensionMap.end())
85         {
86             extensions = extensionMapIterator->second;
87             return true;
88         }
89     }
90 
91     return false;
92 }
93 
94 /**
95  * @brief Write host firmware well-known name
96  *
97  * A wrapper around std::filesystem::create_symlink that avoids EEXIST by
98  * deleting any pre-existing file.
99  *
100  * @param[in] linkTarget The link target argument to
101  * std::filesystem::create_symlink
102  * @param[in] linkPath The link path argument to std::filesystem::create_symlink
103  * @param[in] errorCallback A callback made in the event of filesystem errors.
104  */
105 void writeLink(const std::filesystem::path& linkTarget,
106                const std::filesystem::path& linkPath,
107                const ErrorCallbackType& errorCallback)
108 {
109     std::error_code ec;
110 
111     // remove files with the same name as the symlink to be created,
112     // otherwise symlink will fail with EEXIST.
113     if (!std::filesystem::remove(linkPath, ec))
114     {
115         if (ec)
116         {
117             makeCallback(errorCallback, linkPath, ec);
118             return;
119         }
120     }
121 
122     std::filesystem::create_symlink(linkTarget, linkPath, ec);
123     if (ec)
124     {
125         makeCallback(errorCallback, linkPath, ec);
126         return;
127     }
128 }
129 
130 /**
131  * @brief Find host firmware blob files that need well-known names
132  *
133  * The IBM host firmware runtime looks for data and/or additional code while
134  * bootstraping in files with well-known names.  findLinks uses the provided
135  * extensions argument to find host firmware blob files that require a
136  * well-known name.  When a blob is found, issue the provided callback
137  * (typically a function that will write a symlink).
138  *
139  * @param[in] hostFirmwareDirectory The directory in which findLinks should
140  * look for host firmware blob files that need well-known names.
141  * @param[in] extentions The extensions of the firmware blob files denote a
142  * host firmware blob file requires a well-known name.
143  * @param[in] errorCallback A callback made in the event of filesystem errors.
144  * @param[in] linkCallback A callback made when host firmware blob files
145  * needing a well known name are found.
146  */
147 void findLinks(const std::filesystem::path& hostFirmwareDirectory,
148                const std::vector<std::string>& extensions,
149                const ErrorCallbackType& errorCallback,
150                const LinkCallbackType& linkCallback)
151 {
152     std::error_code ec;
153     std::filesystem::directory_iterator directoryIterator(hostFirmwareDirectory,
154                                                           ec);
155     if (ec)
156     {
157         makeCallback(errorCallback, hostFirmwareDirectory, ec);
158         return;
159     }
160 
161     // Create a symlink from HBB to the corresponding LID file if it exists
162     static const auto hbbLid = "81e0065a.lid";
163     auto hbbLidPath = hostFirmwareDirectory / hbbLid;
164     if (std::filesystem::exists(hbbLidPath))
165     {
166         static const auto hbbName = "HBB";
167         auto hbbLinkPath = hostFirmwareDirectory / hbbName;
168         makeCallback(linkCallback, hbbLid, hbbLinkPath, errorCallback);
169     }
170 
171     for (; directoryIterator != std::filesystem::end(directoryIterator);
172          directoryIterator.increment(ec))
173     {
174         const auto& file = directoryIterator->path();
175         if (ec)
176         {
177             makeCallback(errorCallback, file, ec);
178             // quit here if the increment call failed otherwise the loop may
179             // never finish
180             break;
181         }
182 
183         if (std::find(extensions.begin(), extensions.end(), file.extension()) ==
184             extensions.end())
185         {
186             // this file doesn't have an extension or doesn't match any of the
187             // provided extensions.
188             continue;
189         }
190 
191         auto linkPath(file.parent_path().append(
192             static_cast<const std::string&>(file.stem())));
193 
194         makeCallback(linkCallback, file.filename(), linkPath, errorCallback);
195     }
196 }
197 
198 /**
199  * @brief Set the bios attribute table with details of the host firmware data
200  * for this system.
201  */
202 void setBiosAttr()
203 {
204     std::string biosAttrStr{};
205 
206     constexpr auto biosConfigPath = "/xyz/openbmc_project/bios_config/manager";
207     constexpr auto biosConfigIntf = "xyz.openbmc_project.BIOSConfig.Manager";
208     constexpr auto dbusAttrName = "hb_lid_ids";
209     constexpr auto dbusAttrType =
210         "xyz.openbmc_project.BIOSConfig.Manager.AttributeType.String";
211 
212     using PendingAttributesType = std::vector<std::pair<
213         std::string, std::tuple<std::string, std::variant<std::string>>>>;
214     PendingAttributesType pendingAttributes;
215     pendingAttributes.emplace_back(std::make_pair(
216         dbusAttrName, std::make_tuple(dbusAttrType, biosAttrStr)));
217 
218     auto bus = sdbusplus::bus::new_default();
219     auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
220                                       MAPPER_INTERFACE, "GetObject");
221     method.append(biosConfigPath, std::vector<std::string>({biosConfigIntf}));
222     std::vector<std::pair<std::string, std::vector<std::string>>> response;
223     try
224     {
225         auto reply = bus.call(method);
226         reply.read(response);
227         if (response.empty())
228         {
229             log<level::ERR>("Error reading mapper response",
230                             entry("PATH=%s", biosConfigPath),
231                             entry("INTERFACE=%s", biosConfigIntf));
232             return;
233         }
234         auto method = bus.new_method_call((response.begin()->first).c_str(),
235                                           biosConfigPath,
236                                           SYSTEMD_PROPERTY_INTERFACE, "Set");
237         method.append(biosConfigIntf, "PendingAttributes",
238                       std::variant<PendingAttributesType>(pendingAttributes));
239         bus.call(method);
240     }
241     catch (const sdbusplus::exception::SdBusError& e)
242     {
243         log<level::ERR>("Error setting the bios attribute",
244                         entry("ERROR=%s", e.what()),
245                         entry("ATTRIBUTE=%s", dbusAttrName));
246         return;
247     }
248 }
249 
250 /**
251  * @brief Make callbacks on
252  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
253  *
254  * Look for an instance of
255  * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
256  * argument and if found, issue the provided callback.
257  *
258  * @param[in] interfacesAndProperties the interfaces in which to look for an
259  * instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem
260  * @param[in] callback the user callback to make if
261  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
262  * interfacesAndProperties
263  * @return true if interfacesAndProperties contained an instance of
264  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
265  */
266 bool maybeCall(const std::map<std::string,
267                               std::map<std::string,
268                                        std::variant<std::vector<std::string>>>>&
269                    interfacesAndProperties,
270                const MaybeCallCallbackType& callback)
271 {
272     using namespace std::string_literals;
273 
274     static const auto interfaceName =
275         "xyz.openbmc_project.Configuration.IBMCompatibleSystem"s;
276     auto interfaceIterator = interfacesAndProperties.find(interfaceName);
277     if (interfaceIterator == interfacesAndProperties.cend())
278     {
279         // IBMCompatibleSystem interface not found, so instruct the caller to
280         // keep waiting or try again later.
281         return false;
282     }
283     auto propertyIterator = interfaceIterator->second.find("Names"s);
284     if (propertyIterator == interfaceIterator->second.cend())
285     {
286         // The interface exists but the property doesn't.  This is a bug in the
287         // IBMCompatibleSystem implementation.  The caller should not try
288         // again.
289         std::cerr << "Names property not implemented on " << interfaceName
290                   << "\n";
291         return true;
292     }
293 
294     const auto& ibmCompatibleSystem =
295         std::get<std::vector<std::string>>(propertyIterator->second);
296     if (callback)
297     {
298         callback(ibmCompatibleSystem);
299     }
300 
301     // IBMCompatibleSystem found and callback issued.
302     return true;
303 }
304 
305 /**
306  * @brief Make callbacks on
307  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
308  *
309  * Look for an instance of
310  * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
311  * argument and if found, issue the provided callback.
312  *
313  * @param[in] message the DBus message in which to look for an instance of
314  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
315  * @param[in] callback the user callback to make if
316  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
317  * message
318  * @return true if message contained an instance of
319  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
320  */
321 bool maybeCallMessage(sdbusplus::message::message& message,
322                       const MaybeCallCallbackType& callback)
323 {
324     std::map<std::string,
325              std::map<std::string, std::variant<std::vector<std::string>>>>
326         interfacesAndProperties;
327     sdbusplus::message::object_path _;
328     message.read(_, interfacesAndProperties);
329     return maybeCall(interfacesAndProperties, callback);
330 }
331 
332 /**
333  * @brief Determine system support for host firmware well-known names.
334  *
335  * Using the provided extensionMap and
336  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if
337  * well-known names for host firmare blob files are necessary and if so, create
338  * them.
339  *
340  * @param[in] extensionMap a map of
341  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
342  * file extensions.
343  * @param[in] hostFirmwareDirectory The directory in which findLinks should
344  * look for host firmware blob files that need well-known names.
345  * @param[in] ibmCompatibleSystem The names property of an instance of
346  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
347  * @param[in] errorCallback A callback made in the event of filesystem errors.
348  */
349 void maybeMakeLinks(
350     const std::map<std::string, std::vector<std::string>>& extensionMap,
351     const std::filesystem::path& hostFirmwareDirectory,
352     const std::vector<std::string>& ibmCompatibleSystem,
353     const ErrorCallbackType& errorCallback)
354 {
355     std::vector<std::string> extensions;
356     if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
357                                             extensions))
358     {
359         findLinks(hostFirmwareDirectory, extensions, errorCallback, writeLink);
360     }
361 }
362 
363 /**
364  * @brief Determine system support for updating the bios attribute table.
365  *
366  * Using the provided extensionMap and
367  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if the bios
368  * attribute table needs to be updated.
369  *
370  * @param[in] extensionMap a map of
371  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
372  * file extensions.
373  * @param[in] ibmCompatibleSystem The names property of an instance of
374  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
375  */
376 void maybeSetBiosAttr(
377     const std::map<std::string, std::vector<std::string>>& extensionMap,
378     const std::vector<std::string>& ibmCompatibleSystem)
379 {
380     std::vector<std::string> extensions;
381     if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
382                                             extensions))
383     {
384         setBiosAttr();
385     }
386 }
387 
388 /**
389  * @brief process host firmware
390  *
391  * Allocate a callback context and register for DBus.ObjectManager Interfaces
392  * added signals from entity manager.
393  *
394  * Check the current entity manager object tree for a
395  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instance (entity
396  * manager will be dbus activated if it is not running).  If one is found,
397  * determine if symlinks need to be created and create them.  Instruct the
398  * program event loop to exit.
399  *
400  * If no instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
401  * found return the callback context to main, where the program will sleep
402  * until the callback is invoked one or more times and instructs the program
403  * event loop to exit when
404  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is added.
405  *
406  * @param[in] bus a DBus client connection
407  * @param[in] extensionMap a map of
408  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
409  * file extensions.
410  * @param[in] hostFirmwareDirectory The directory in which processHostFirmware
411  * should look for blob files.
412  * @param[in] errorCallback A callback made in the event of filesystem errors.
413  * @param[in] loop a program event loop
414  * @return nullptr if an instance of
415  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found, otherwise a
416  * pointer to an sdbusplus match object.
417  */
418 std::shared_ptr<void> processHostFirmware(
419     sdbusplus::bus::bus& bus,
420     std::map<std::string, std::vector<std::string>> extensionMap,
421     std::filesystem::path hostFirmwareDirectory,
422     ErrorCallbackType errorCallback, sdeventplus::Event& loop)
423 {
424     // ownership of extensionMap, hostFirmwareDirectory and errorCallback can't
425     // be transfered to the match callback because they are needed in the non
426     // async part of this function below, so they need to be moved to the heap.
427     auto pExtensionMap =
428         std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
429     auto pHostFirmwareDirectory =
430         std::make_shared<decltype(hostFirmwareDirectory)>(
431             std::move(hostFirmwareDirectory));
432     auto pErrorCallback =
433         std::make_shared<decltype(errorCallback)>(std::move(errorCallback));
434 
435     // register for a callback in case the IBMCompatibleSystem interface has
436     // not yet been published by entity manager.
437     auto interfacesAddedMatch = std::make_shared<sdbusplus::bus::match::match>(
438         bus,
439         sdbusplus::bus::match::rules::interfacesAdded() +
440             sdbusplus::bus::match::rules::sender(
441                 "xyz.openbmc_project.EntityManager"),
442         [pExtensionMap, pHostFirmwareDirectory, pErrorCallback,
443          &loop](auto& message) {
444             // bind the extension map, host firmware directory, and error
445             // callback to the maybeMakeLinks function.
446             auto maybeMakeLinksWithArgsBound =
447                 std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
448                           std::cref(*pHostFirmwareDirectory),
449                           std::placeholders::_1, std::cref(*pErrorCallback));
450 
451             // if the InterfacesAdded message contains an an instance of
452             // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to
453             // see if links are necessary on this system and if so, create
454             // them.
455             if (maybeCallMessage(message, maybeMakeLinksWithArgsBound))
456             {
457                 // The IBMCompatibleSystem interface was found and the links
458                 // were created if applicable.  Instruct the event loop /
459                 // subcommand to exit.
460                 loop.exit(0);
461             }
462         });
463 
464     // now that we'll get a callback in the event of an InterfacesAdded signal
465     // (potentially containing
466     // xyz.openbmc_project.Configuration.IBMCompatibleSystem), activate entity
467     // manager if it isn't running and enumerate its objects
468     auto getManagedObjects = bus.new_method_call(
469         "xyz.openbmc_project.EntityManager", "/",
470         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
471     std::map<std::string,
472              std::map<std::string, std::variant<std::vector<std::string>>>>
473         interfacesAndProperties;
474     std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)>
475         objects;
476     try
477     {
478         auto reply = bus.call(getManagedObjects);
479         reply.read(objects);
480     }
481     catch (const sdbusplus::exception::SdBusError& e)
482     {
483         // Error querying the EntityManager interface. Return the match to have
484         // the callback run if/when the interface appears in D-Bus.
485         return interfacesAddedMatch;
486     }
487 
488     // bind the extension map, host firmware directory, and error callback to
489     // the maybeMakeLinks function.
490     auto maybeMakeLinksWithArgsBound =
491         std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
492                   std::cref(*pHostFirmwareDirectory), std::placeholders::_1,
493                   std::cref(*pErrorCallback));
494 
495     for (const auto& pair : objects)
496     {
497         std::tie(std::ignore, interfacesAndProperties) = pair;
498         // if interfacesAndProperties contains an an instance of
499         // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to see
500         // if links are necessary on this system and if so, create them
501         if (maybeCall(interfacesAndProperties, maybeMakeLinksWithArgsBound))
502         {
503             // The IBMCompatibleSystem interface is already on the bus and the
504             // links were created if applicable.  Instruct the event loop to
505             // exit.
506             loop.exit(0);
507             // The match object isn't needed anymore, so destroy it on return.
508             return nullptr;
509         }
510     }
511 
512     // The IBMCompatibleSystem interface has not yet been published.  Move
513     // ownership of the match callback to the caller.
514     return interfacesAddedMatch;
515 }
516 
517 /**
518  * @brief Update the Bios Attribute Table
519  *
520  * If an instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
521  * found, update the Bios Attribute Table with the appropriate host firmware
522  * data.
523  *
524  * @param[in] bus - D-Bus client connection.
525  * @param[in] extensionMap - Map of IBMCompatibleSystem names and host firmware
526  *                           file extensions.
527  * @param[in] loop - Program event loop.
528  * @return nullptr
529  */
530 std::shared_ptr<void> updateBiosAttrTable(
531     sdbusplus::bus::bus& bus,
532     std::map<std::string, std::vector<std::string>> extensionMap,
533     sdeventplus::Event& loop)
534 {
535     auto pExtensionMap =
536         std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
537 
538     auto getManagedObjects = bus.new_method_call(
539         "xyz.openbmc_project.EntityManager", "/",
540         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
541     std::map<std::string,
542              std::map<std::string, std::variant<std::vector<std::string>>>>
543         interfacesAndProperties;
544     std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)>
545         objects;
546     try
547     {
548         auto reply = bus.call(getManagedObjects);
549         reply.read(objects);
550     }
551     catch (const sdbusplus::exception::SdBusError& e)
552     {}
553 
554     auto maybeSetAttrWithArgsBound = std::bind(
555         maybeSetBiosAttr, std::cref(*pExtensionMap), std::placeholders::_1);
556 
557     for (const auto& pair : objects)
558     {
559         std::tie(std::ignore, interfacesAndProperties) = pair;
560         if (maybeCall(interfacesAndProperties, maybeSetAttrWithArgsBound))
561         {
562             break;
563         }
564     }
565 
566     loop.exit(0);
567     return nullptr;
568 }
569 
570 } // namespace process_hostfirmware
571 } // namespace functions
572