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 <nlohmann/json.hpp>
10 #include <phosphor-logging/log.hpp>
11 #include <sdbusplus/bus.hpp>
12 #include <sdbusplus/bus/match.hpp>
13 #include <sdbusplus/exception.hpp>
14 #include <sdbusplus/message.hpp>
15 #include <sdeventplus/event.hpp>
16 
17 #include <filesystem>
18 #include <fstream>
19 #include <functional>
20 #include <iostream>
21 #include <map>
22 #include <memory>
23 #include <string>
24 #include <variant>
25 #include <vector>
26 
27 namespace functions
28 {
29 namespace process_hostfirmware
30 {
31 
32 using namespace phosphor::logging;
33 using InterfacesPropertiesMap =
34     std::map<std::string,
35              std::map<std::string, std::variant<std::vector<std::string>>>>;
36 using ManagedObjectType =
37     std::map<sdbusplus::message::object_path, InterfacesPropertiesMap>;
38 
39 /**
40  * @brief GetObject function to find the service given an object path.
41  *        It is used to determine if a service is running, so there is no need
42  *        to specify interfaces as a parameter to constrain the search.
43  */
44 std::string getObject(sdbusplus::bus::bus& bus, const std::string& path)
45 {
46     auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
47                                       MAPPER_BUSNAME, "GetObject");
48     method.append(path);
49     std::vector<std::string> interfaces;
50     method.append(interfaces);
51 
52     std::vector<std::pair<std::string, std::vector<std::string>>> response;
53 
54     try
55     {
56         auto reply = bus.call(method);
57         reply.read(response);
58         if (response.empty())
59         {
60             return std::string{};
61         }
62     }
63     catch (const sdbusplus::exception::SdBusError& e)
64     {
65         return std::string{};
66     }
67     return response[0].first;
68 }
69 
70 /**
71  * @brief Returns the managed objects for a given service
72  */
73 ManagedObjectType getManagedObjects(sdbusplus::bus::bus& bus,
74                                     const std::string& service)
75 {
76     auto method = bus.new_method_call(service.c_str(), "/",
77                                       "org.freedesktop.DBus.ObjectManager",
78                                       "GetManagedObjects");
79 
80     ManagedObjectType objects;
81 
82     try
83     {
84         auto reply = bus.call(method);
85         reply.read(objects);
86     }
87     catch (const sdbusplus::exception::SdBusError& e)
88     {
89         return ManagedObjectType{};
90     }
91     return objects;
92 }
93 
94 /**
95  * @brief Issue callbacks safely
96  *
97  * std::function can be empty, so this wrapper method checks for that prior to
98  * calling it to avoid std::bad_function_call
99  *
100  * @tparam Sig the types of the std::function arguments
101  * @tparam Args the deduced argument types
102  * @param[in] callback the callback being wrapped
103  * @param[in] args the callback arguments
104  */
105 template <typename... Sig, typename... Args>
106 void makeCallback(const std::function<void(Sig...)>& callback, Args&&... args)
107 {
108     if (callback)
109     {
110         callback(std::forward<Args>(args)...);
111     }
112 }
113 
114 /**
115  * @brief Get file extensions for IBMCompatibleSystem
116  *
117  * IBM host firmware can be deployed as blobs (files) in a filesystem.  Host
118  * firmware blobs for different values of
119  * xyz.openbmc_project.Configuration.IBMCompatibleSystem are packaged with
120  * different filename extensions.  getExtensionsForIbmCompatibleSystem
121  * maintains the mapping from a given value of
122  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to an array of
123  * filename extensions.
124  *
125  * If a mapping is found getExtensionsForIbmCompatibleSystem returns true and
126  * the extensions parameter is reset with the map entry.  If no mapping is
127  * found getExtensionsForIbmCompatibleSystem returns false and extensions is
128  * unmodified.
129  *
130  * @param[in] extensionMap a map of
131  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
132  * file extensions.
133  * @param[in] ibmCompatibleSystem The names property of an instance of
134  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
135  * @param[out] extentions the host firmware blob file extensions
136  * @return true if an entry was found, otherwise false
137  */
138 bool getExtensionsForIbmCompatibleSystem(
139     const std::map<std::string, std::vector<std::string>>& extensionMap,
140     const std::vector<std::string>& ibmCompatibleSystem,
141     std::vector<std::string>& extensions)
142 {
143     for (const auto& system : ibmCompatibleSystem)
144     {
145         auto extensionMapIterator = extensionMap.find(system);
146         if (extensionMapIterator != extensionMap.end())
147         {
148             extensions = extensionMapIterator->second;
149             return true;
150         }
151     }
152 
153     return false;
154 }
155 
156 /**
157  * @brief Write host firmware well-known name
158  *
159  * A wrapper around std::filesystem::create_symlink that avoids EEXIST by
160  * deleting any pre-existing file.
161  *
162  * @param[in] linkTarget The link target argument to
163  * std::filesystem::create_symlink
164  * @param[in] linkPath The link path argument to std::filesystem::create_symlink
165  * @param[in] errorCallback A callback made in the event of filesystem errors.
166  */
167 void writeLink(const std::filesystem::path& linkTarget,
168                const std::filesystem::path& linkPath,
169                const ErrorCallbackType& errorCallback)
170 {
171     std::error_code ec;
172 
173     // remove files with the same name as the symlink to be created,
174     // otherwise symlink will fail with EEXIST.
175     if (!std::filesystem::remove(linkPath, ec))
176     {
177         if (ec)
178         {
179             makeCallback(errorCallback, linkPath, ec);
180             return;
181         }
182     }
183 
184     std::filesystem::create_symlink(linkTarget, linkPath, ec);
185     if (ec)
186     {
187         makeCallback(errorCallback, linkPath, ec);
188         return;
189     }
190 }
191 
192 /**
193  * @brief Find host firmware blob files that need well-known names
194  *
195  * The IBM host firmware runtime looks for data and/or additional code while
196  * bootstraping in files with well-known names.  findLinks uses the provided
197  * extensions argument to find host firmware blob files that require a
198  * well-known name.  When a blob is found, issue the provided callback
199  * (typically a function that will write a symlink).
200  *
201  * @param[in] hostFirmwareDirectory The directory in which findLinks should
202  * look for host firmware blob files that need well-known names.
203  * @param[in] extentions The extensions of the firmware blob files denote a
204  * host firmware blob file requires a well-known name.
205  * @param[in] errorCallback A callback made in the event of filesystem errors.
206  * @param[in] linkCallback A callback made when host firmware blob files
207  * needing a well known name are found.
208  */
209 void findLinks(const std::filesystem::path& hostFirmwareDirectory,
210                const std::vector<std::string>& extensions,
211                const ErrorCallbackType& errorCallback,
212                const LinkCallbackType& linkCallback)
213 {
214     std::error_code ec;
215     std::filesystem::directory_iterator directoryIterator(hostFirmwareDirectory,
216                                                           ec);
217     if (ec)
218     {
219         makeCallback(errorCallback, hostFirmwareDirectory, ec);
220         return;
221     }
222 
223     for (; directoryIterator != std::filesystem::end(directoryIterator);
224          directoryIterator.increment(ec))
225     {
226         const auto& file = directoryIterator->path();
227         if (ec)
228         {
229             makeCallback(errorCallback, file, ec);
230             // quit here if the increment call failed otherwise the loop may
231             // never finish
232             break;
233         }
234 
235         if (std::find(extensions.begin(), extensions.end(), file.extension()) ==
236             extensions.end())
237         {
238             // this file doesn't have an extension or doesn't match any of the
239             // provided extensions.
240             continue;
241         }
242 
243         auto linkPath(file.parent_path().append(
244             static_cast<const std::string&>(file.stem())));
245 
246         makeCallback(linkCallback, file.filename(), linkPath, errorCallback);
247     }
248 }
249 
250 /**
251  * @brief Parse the elements json file and construct a string with the data to
252  *        be used to update the bios attribute table.
253  *
254  * @param[in] elementsJsonFilePath - The path to the host firmware json file.
255  * @param[in] extensions - The extensions of the firmware blob files.
256  */
257 std::string getBiosAttrStr(const std::filesystem::path& elementsJsonFilePath,
258                            const std::vector<std::string>& extensions)
259 {
260     std::string biosAttrStr{};
261 
262     std::ifstream jsonFile(elementsJsonFilePath.c_str());
263     if (!jsonFile)
264     {
265         return {};
266     }
267 
268     std::map<std::string, std::string> attr;
269     auto data = nlohmann::json::parse(jsonFile, nullptr, false);
270     if (data.is_discarded())
271     {
272         log<level::ERR>("Error parsing JSON file",
273                         entry("FILE=%s", elementsJsonFilePath.c_str()));
274         return {};
275     }
276 
277     // .get requires a non-const iterator
278     for (auto& iter : data["lids"])
279     {
280         std::string name{};
281         std::string lid{};
282 
283         try
284         {
285             name = iter["element_name"].get<std::string>();
286             lid = iter["short_lid_name"].get<std::string>();
287         }
288         catch (std::exception& e)
289         {
290             // Possibly the element or lid name field was not found
291             log<level::ERR>("Error reading JSON field",
292                             entry("FILE=%s", elementsJsonFilePath.c_str()),
293                             entry("ERROR=%s", e.what()));
294             continue;
295         }
296 
297         // The elements with the ipl extension have higher priority. Therefore
298         // Use operator[] to overwrite value if an entry for it already exists.
299         // Ex: if the JSON contains an entry A.P10 followed by A.P10.iplTime,
300         // the lid value for the latter one will be overwrite the value of the
301         // first one.
302         constexpr auto iplExtension = ".iplTime";
303         std::filesystem::path path(name);
304         if (path.extension() == iplExtension)
305         {
306             // Some elements have an additional extension, ex: .P10.iplTime
307             // Strip off the ipl extension with stem(), then check if there is
308             // an additional extension with extension().
309             if (!path.stem().extension().empty())
310             {
311                 // Check if the extension matches the extensions for this system
312                 if (std::find(extensions.begin(), extensions.end(),
313                               path.stem().extension()) == extensions.end())
314                 {
315                     continue;
316                 }
317             }
318             // Get the element name without extensions by calling stem() twice
319             // since stem() returns the base name if no periods are found.
320             // Therefore both "element.P10" and "element.P10.iplTime" would
321             // become "element".
322             attr[path.stem().stem()] = lid;
323             continue;
324         }
325 
326         // Process all other extensions. The extension should match the list of
327         // supported extensions for this system. Use .insert() to only add
328         // entries that do not exist, so to not overwrite the values that may
329         // had been added that had the ipl extension.
330         if (std::find(extensions.begin(), extensions.end(), path.extension()) !=
331             extensions.end())
332         {
333             attr.insert({path.stem(), lid});
334         }
335     }
336     for (const auto& a : attr)
337     {
338         // Build the bios attribute string with format:
339         // "element1=lid1,element2=lid2,elementN=lidN,"
340         biosAttrStr += a.first + "=" + a.second + ",";
341 
342         // Create symlinks from the hostfw elements to their corresponding
343         // lid files if they don't exist
344         auto elementFilePath =
345             std::filesystem::path("/media/hostfw/running") / a.first;
346         if (!std::filesystem::exists(elementFilePath))
347         {
348             std::error_code ec;
349             auto lidName = a.second + ".lid";
350             std::filesystem::create_symlink(lidName, elementFilePath, ec);
351             if (ec)
352             {
353                 log<level::ERR>("Error creating symlink",
354                                 entry("TARGET=%s", lidName.c_str()),
355                                 entry("LINK=%s", elementFilePath.c_str()));
356             }
357         }
358     }
359 
360     return biosAttrStr;
361 }
362 
363 /**
364  * @brief Set the bios attribute table with details of the host firmware data
365  * for this system.
366  *
367  * @param[in] elementsJsonFilePath - The path to the host firmware json file.
368  * @param[in] extentions - The extensions of the firmware blob files.
369  */
370 void setBiosAttr(const std::filesystem::path& elementsJsonFilePath,
371                  const std::vector<std::string>& extensions)
372 {
373     auto biosAttrStr = getBiosAttrStr(elementsJsonFilePath, extensions);
374 
375     constexpr auto biosConfigPath = "/xyz/openbmc_project/bios_config/manager";
376     constexpr auto biosConfigIntf = "xyz.openbmc_project.BIOSConfig.Manager";
377     constexpr auto dbusAttrName = "hb_lid_ids";
378     constexpr auto dbusAttrType =
379         "xyz.openbmc_project.BIOSConfig.Manager.AttributeType.String";
380 
381     using PendingAttributesType = std::vector<std::pair<
382         std::string, std::tuple<std::string, std::variant<std::string>>>>;
383     PendingAttributesType pendingAttributes;
384     pendingAttributes.emplace_back(std::make_pair(
385         dbusAttrName, std::make_tuple(dbusAttrType, biosAttrStr)));
386 
387     auto bus = sdbusplus::bus::new_default();
388     auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
389                                       MAPPER_INTERFACE, "GetObject");
390     method.append(biosConfigPath, std::vector<std::string>({biosConfigIntf}));
391     std::vector<std::pair<std::string, std::vector<std::string>>> response;
392     try
393     {
394         auto reply = bus.call(method);
395         reply.read(response);
396         if (response.empty())
397         {
398             log<level::ERR>("Error reading mapper response",
399                             entry("PATH=%s", biosConfigPath),
400                             entry("INTERFACE=%s", biosConfigIntf));
401             return;
402         }
403         auto method = bus.new_method_call((response.begin()->first).c_str(),
404                                           biosConfigPath,
405                                           SYSTEMD_PROPERTY_INTERFACE, "Set");
406         method.append(biosConfigIntf, "PendingAttributes",
407                       std::variant<PendingAttributesType>(pendingAttributes));
408         bus.call(method);
409     }
410     catch (const sdbusplus::exception::SdBusError& e)
411     {
412         log<level::ERR>("Error setting the bios attribute",
413                         entry("ERROR=%s", e.what()),
414                         entry("ATTRIBUTE=%s", dbusAttrName));
415         return;
416     }
417 }
418 
419 /**
420  * @brief Make callbacks on
421  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
422  *
423  * Look for an instance of
424  * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
425  * argument and if found, issue the provided callback.
426  *
427  * @param[in] interfacesAndProperties the interfaces in which to look for an
428  * instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem
429  * @param[in] callback the user callback to make if
430  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
431  * interfacesAndProperties
432  * @return true if interfacesAndProperties contained an instance of
433  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
434  */
435 bool maybeCall(const std::map<std::string,
436                               std::map<std::string,
437                                        std::variant<std::vector<std::string>>>>&
438                    interfacesAndProperties,
439                const MaybeCallCallbackType& callback)
440 {
441     using namespace std::string_literals;
442 
443     static const auto interfaceName =
444         "xyz.openbmc_project.Configuration.IBMCompatibleSystem"s;
445     auto interfaceIterator = interfacesAndProperties.find(interfaceName);
446     if (interfaceIterator == interfacesAndProperties.cend())
447     {
448         // IBMCompatibleSystem interface not found, so instruct the caller to
449         // keep waiting or try again later.
450         return false;
451     }
452     auto propertyIterator = interfaceIterator->second.find("Names"s);
453     if (propertyIterator == interfaceIterator->second.cend())
454     {
455         // The interface exists but the property doesn't.  This is a bug in the
456         // IBMCompatibleSystem implementation.  The caller should not try
457         // again.
458         std::cerr << "Names property not implemented on " << interfaceName
459                   << "\n";
460         return true;
461     }
462 
463     const auto& ibmCompatibleSystem =
464         std::get<std::vector<std::string>>(propertyIterator->second);
465     if (callback)
466     {
467         callback(ibmCompatibleSystem);
468     }
469 
470     // IBMCompatibleSystem found and callback issued.
471     return true;
472 }
473 
474 /**
475  * @brief Make callbacks on
476  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
477  *
478  * Look for an instance of
479  * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
480  * argument and if found, issue the provided callback.
481  *
482  * @param[in] message the DBus message in which to look for an instance of
483  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
484  * @param[in] callback the user callback to make if
485  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
486  * message
487  * @return true if message contained an instance of
488  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
489  */
490 bool maybeCallMessage(sdbusplus::message::message& message,
491                       const MaybeCallCallbackType& callback)
492 {
493     std::map<std::string,
494              std::map<std::string, std::variant<std::vector<std::string>>>>
495         interfacesAndProperties;
496     sdbusplus::message::object_path _;
497     message.read(_, interfacesAndProperties);
498     return maybeCall(interfacesAndProperties, callback);
499 }
500 
501 /**
502  * @brief Determine system support for host firmware well-known names.
503  *
504  * Using the provided extensionMap and
505  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if
506  * well-known names for host firmare blob files are necessary and if so, create
507  * them.
508  *
509  * @param[in] extensionMap a map of
510  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
511  * file extensions.
512  * @param[in] hostFirmwareDirectory The directory in which findLinks should
513  * look for host firmware blob files that need well-known names.
514  * @param[in] ibmCompatibleSystem The names property of an instance of
515  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
516  * @param[in] errorCallback A callback made in the event of filesystem errors.
517  */
518 void maybeMakeLinks(
519     const std::map<std::string, std::vector<std::string>>& extensionMap,
520     const std::filesystem::path& hostFirmwareDirectory,
521     const std::vector<std::string>& ibmCompatibleSystem,
522     const ErrorCallbackType& errorCallback)
523 {
524     std::vector<std::string> extensions;
525     if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
526                                             extensions))
527     {
528         findLinks(hostFirmwareDirectory, extensions, errorCallback, writeLink);
529     }
530 }
531 
532 /**
533  * @brief Determine system support for updating the bios attribute table.
534  *
535  * Using the provided extensionMap and
536  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if the bios
537  * attribute table needs to be updated.
538  *
539  * @param[in] extensionMap a map of
540  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
541  * file extensions.
542  * @param[in] elementsJsonFilePath The file path to the json file
543  * @param[in] ibmCompatibleSystem The names property of an instance of
544  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
545  */
546 void maybeSetBiosAttr(
547     const std::map<std::string, std::vector<std::string>>& extensionMap,
548     const std::filesystem::path& elementsJsonFilePath,
549     const std::vector<std::string>& ibmCompatibleSystem)
550 {
551     std::vector<std::string> extensions;
552     if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
553                                             extensions))
554     {
555         setBiosAttr(elementsJsonFilePath, extensions);
556     }
557 }
558 
559 /**
560  * @brief process host firmware
561  *
562  * Allocate a callback context and register for DBus.ObjectManager Interfaces
563  * added signals from entity manager.
564  *
565  * Check the current entity manager object tree for a
566  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instance (entity
567  * manager will be dbus activated if it is not running).  If one is found,
568  * determine if symlinks need to be created and create them.  Instruct the
569  * program event loop to exit.
570  *
571  * If no instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
572  * found return the callback context to main, where the program will sleep
573  * until the callback is invoked one or more times and instructs the program
574  * event loop to exit when
575  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is added.
576  *
577  * @param[in] bus a DBus client connection
578  * @param[in] extensionMap a map of
579  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
580  * file extensions.
581  * @param[in] hostFirmwareDirectory The directory in which processHostFirmware
582  * should look for blob files.
583  * @param[in] errorCallback A callback made in the event of filesystem errors.
584  * @param[in] loop a program event loop
585  * @return nullptr if an instance of
586  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found, otherwise a
587  * pointer to an sdbusplus match object.
588  */
589 std::shared_ptr<void> processHostFirmware(
590     sdbusplus::bus::bus& bus,
591     std::map<std::string, std::vector<std::string>> extensionMap,
592     std::filesystem::path hostFirmwareDirectory,
593     ErrorCallbackType errorCallback, sdeventplus::Event& loop)
594 {
595     // ownership of extensionMap, hostFirmwareDirectory and errorCallback can't
596     // be transfered to the match callback because they are needed in the non
597     // async part of this function below, so they need to be moved to the heap.
598     auto pExtensionMap =
599         std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
600     auto pHostFirmwareDirectory =
601         std::make_shared<decltype(hostFirmwareDirectory)>(
602             std::move(hostFirmwareDirectory));
603     auto pErrorCallback =
604         std::make_shared<decltype(errorCallback)>(std::move(errorCallback));
605 
606     // register for a callback in case the IBMCompatibleSystem interface has
607     // not yet been published by entity manager.
608     auto interfacesAddedMatch = std::make_shared<sdbusplus::bus::match::match>(
609         bus,
610         sdbusplus::bus::match::rules::interfacesAdded() +
611             sdbusplus::bus::match::rules::sender(
612                 "xyz.openbmc_project.EntityManager"),
613         [pExtensionMap, pHostFirmwareDirectory, pErrorCallback,
614          &loop](auto& message) {
615             // bind the extension map, host firmware directory, and error
616             // callback to the maybeMakeLinks function.
617             auto maybeMakeLinksWithArgsBound =
618                 std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
619                           std::cref(*pHostFirmwareDirectory),
620                           std::placeholders::_1, std::cref(*pErrorCallback));
621 
622             // if the InterfacesAdded message contains an an instance of
623             // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to
624             // see if links are necessary on this system and if so, create
625             // them.
626             if (maybeCallMessage(message, maybeMakeLinksWithArgsBound))
627             {
628                 // The IBMCompatibleSystem interface was found and the links
629                 // were created if applicable.  Instruct the event loop /
630                 // subcommand to exit.
631                 loop.exit(0);
632             }
633         });
634 
635     // now that we'll get a callback in the event of an InterfacesAdded signal
636     // (potentially containing
637     // xyz.openbmc_project.Configuration.IBMCompatibleSystem), activate entity
638     // manager if it isn't running and enumerate its objects
639     auto getManagedObjects = bus.new_method_call(
640         "xyz.openbmc_project.EntityManager", "/",
641         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
642     std::map<std::string,
643              std::map<std::string, std::variant<std::vector<std::string>>>>
644         interfacesAndProperties;
645     std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)>
646         objects;
647     try
648     {
649         auto reply = bus.call(getManagedObjects);
650         reply.read(objects);
651     }
652     catch (const sdbusplus::exception::SdBusError& e)
653     {
654         // Error querying the EntityManager interface. Return the match to have
655         // the callback run if/when the interface appears in D-Bus.
656         return interfacesAddedMatch;
657     }
658 
659     // bind the extension map, host firmware directory, and error callback to
660     // the maybeMakeLinks function.
661     auto maybeMakeLinksWithArgsBound =
662         std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
663                   std::cref(*pHostFirmwareDirectory), std::placeholders::_1,
664                   std::cref(*pErrorCallback));
665 
666     for (const auto& pair : objects)
667     {
668         std::tie(std::ignore, interfacesAndProperties) = pair;
669         // if interfacesAndProperties contains an an instance of
670         // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to see
671         // if links are necessary on this system and if so, create them
672         if (maybeCall(interfacesAndProperties, maybeMakeLinksWithArgsBound))
673         {
674             // The IBMCompatibleSystem interface is already on the bus and the
675             // links were created if applicable.  Instruct the event loop to
676             // exit.
677             loop.exit(0);
678             // The match object isn't needed anymore, so destroy it on return.
679             return nullptr;
680         }
681     }
682 
683     // The IBMCompatibleSystem interface has not yet been published.  Move
684     // ownership of the match callback to the caller.
685     return interfacesAddedMatch;
686 }
687 
688 /**
689  * @brief Update the Bios Attribute Table
690  *
691  * If an instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
692  * found, update the Bios Attribute Table with the appropriate host firmware
693  * data.
694  *
695  * @param[in] bus - D-Bus client connection.
696  * @param[in] extensionMap - Map of IBMCompatibleSystem names and host firmware
697  *                           file extensions.
698  * @param[in] elementsJsonFilePath - The Path to the json file
699  * @param[in] loop - Program event loop.
700  * @return nullptr
701  */
702 std::vector<std::shared_ptr<void>> updateBiosAttrTable(
703     sdbusplus::bus::bus& bus,
704     std::map<std::string, std::vector<std::string>> extensionMap,
705     std::filesystem::path elementsJsonFilePath, sdeventplus::Event& loop)
706 {
707     constexpr auto pldmPath = "/xyz/openbmc_project/pldm";
708     constexpr auto entityManagerServiceName =
709         "xyz.openbmc_project.EntityManager";
710 
711     auto pExtensionMap =
712         std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
713     auto pElementsJsonFilePath =
714         std::make_shared<decltype(elementsJsonFilePath)>(
715             std::move(elementsJsonFilePath));
716 
717     // Entity Manager is needed to get the list of supported extensions. Add a
718     // match to monitor interfaces added in case it's not running yet.
719     auto maybeSetAttrWithArgsBound =
720         std::bind(maybeSetBiosAttr, std::cref(*pExtensionMap),
721                   std::cref(*pElementsJsonFilePath), std::placeholders::_1);
722 
723     std::vector<std::shared_ptr<void>> matches;
724     matches.emplace_back(std::make_shared<sdbusplus::bus::match::match>(
725         bus,
726         sdbusplus::bus::match::rules::interfacesAdded() +
727             sdbusplus::bus::match::rules::sender(
728                 "xyz.openbmc_project.EntityManager"),
729         [pldmPath, pExtensionMap, pElementsJsonFilePath,
730          maybeSetAttrWithArgsBound, &loop](auto& message) {
731             auto bus = sdbusplus::bus::new_default();
732             auto pldmObject = getObject(bus, pldmPath);
733             if (pldmObject.empty())
734             {
735                 return;
736             }
737             if (maybeCallMessage(message, maybeSetAttrWithArgsBound))
738             {
739                 loop.exit(0);
740             }
741         }));
742     matches.emplace_back(std::make_shared<sdbusplus::bus::match::match>(
743         bus,
744         sdbusplus::bus::match::rules::nameOwnerChanged() +
745             sdbusplus::bus::match::rules::arg0namespace(
746                 "xyz.openbmc_project.PLDM"),
747         [pExtensionMap, pElementsJsonFilePath, maybeSetAttrWithArgsBound,
748          &loop](auto& message) {
749             std::string name;
750             std::string oldOwner;
751             std::string newOwner;
752             message.read(name, oldOwner, newOwner);
753 
754             if (newOwner.empty())
755             {
756                 return;
757             }
758 
759             auto bus = sdbusplus::bus::new_default();
760             InterfacesPropertiesMap interfacesAndProperties;
761             auto objects = getManagedObjects(bus, entityManagerServiceName);
762             for (const auto& pair : objects)
763             {
764                 std::tie(std::ignore, interfacesAndProperties) = pair;
765                 if (maybeCall(interfacesAndProperties,
766                               maybeSetAttrWithArgsBound))
767                 {
768                     loop.exit(0);
769                 }
770             }
771         }));
772 
773     // The BIOS attribute table can only be updated if PLDM is running because
774     // PLDM is the one that exposes this property. Return if it's not running.
775     auto pldmObject = getObject(bus, pldmPath);
776     if (pldmObject.empty())
777     {
778         return matches;
779     }
780 
781     InterfacesPropertiesMap interfacesAndProperties;
782     auto objects = getManagedObjects(bus, entityManagerServiceName);
783     for (const auto& pair : objects)
784     {
785         std::tie(std::ignore, interfacesAndProperties) = pair;
786         if (maybeCall(interfacesAndProperties, maybeSetAttrWithArgsBound))
787         {
788             loop.exit(0);
789             return {};
790         }
791     }
792 
793     return matches;
794 }
795 
796 } // namespace process_hostfirmware
797 } // namespace functions
798