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::exception& 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::exception& 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     // Create a symlink for pnor.toc
224     static const auto tocLid = "81e00994.lid";
225     auto tocLidPath = hostFirmwareDirectory / tocLid;
226     if (std::filesystem::exists(tocLidPath))
227     {
228         static const auto tocName = "pnor.toc";
229         auto tocLinkPath = hostFirmwareDirectory / tocName;
230         makeCallback(linkCallback, tocLid, tocLinkPath, errorCallback);
231     }
232 
233     for (; directoryIterator != std::filesystem::end(directoryIterator);
234          directoryIterator.increment(ec))
235     {
236         const auto& file = directoryIterator->path();
237         if (ec)
238         {
239             makeCallback(errorCallback, file, ec);
240             // quit here if the increment call failed otherwise the loop may
241             // never finish
242             break;
243         }
244 
245         if (std::find(extensions.begin(), extensions.end(), file.extension()) ==
246             extensions.end())
247         {
248             // this file doesn't have an extension or doesn't match any of the
249             // provided extensions.
250             continue;
251         }
252 
253         auto linkPath(file.parent_path().append(
254             static_cast<const std::string&>(file.stem())));
255 
256         makeCallback(linkCallback, file.filename(), linkPath, errorCallback);
257     }
258 }
259 
260 /**
261  * @brief Parse the elements json file and construct a string with the data to
262  *        be used to update the bios attribute table.
263  *
264  * @param[in] elementsJsonFilePath - The path to the host firmware json file.
265  * @param[in] extensions - The extensions of the firmware blob files.
266  */
267 std::string getBiosAttrStr(const std::filesystem::path& elementsJsonFilePath,
268                            const std::vector<std::string>& extensions)
269 {
270     std::string biosAttrStr{};
271 
272     std::ifstream jsonFile(elementsJsonFilePath.c_str());
273     if (!jsonFile)
274     {
275         return {};
276     }
277 
278     std::map<std::string, std::string> attr;
279     auto data = nlohmann::json::parse(jsonFile, nullptr, false);
280     if (data.is_discarded())
281     {
282         log<level::ERR>("Error parsing JSON file",
283                         entry("FILE=%s", elementsJsonFilePath.c_str()));
284         return {};
285     }
286 
287     // .get requires a non-const iterator
288     for (auto& iter : data["lids"])
289     {
290         std::string name{};
291         std::string lid{};
292 
293         try
294         {
295             name = iter["element_name"].get<std::string>();
296             lid = iter["short_lid_name"].get<std::string>();
297         }
298         catch (std::exception& e)
299         {
300             // Possibly the element or lid name field was not found
301             log<level::ERR>("Error reading JSON field",
302                             entry("FILE=%s", elementsJsonFilePath.c_str()),
303                             entry("ERROR=%s", e.what()));
304             continue;
305         }
306 
307         // The elements with the ipl extension have higher priority. Therefore
308         // Use operator[] to overwrite value if an entry for it already exists.
309         // Ex: if the JSON contains an entry A.P10 followed by A.P10.iplTime,
310         // the lid value for the latter one will be overwrite the value of the
311         // first one.
312         constexpr auto iplExtension = ".iplTime";
313         std::filesystem::path path(name);
314         if (path.extension() == iplExtension)
315         {
316             // Some elements have an additional extension, ex: .P10.iplTime
317             // Strip off the ipl extension with stem(), then check if there is
318             // an additional extension with extension().
319             if (!path.stem().extension().empty())
320             {
321                 // Check if the extension matches the extensions for this system
322                 if (std::find(extensions.begin(), extensions.end(),
323                               path.stem().extension()) == extensions.end())
324                 {
325                     continue;
326                 }
327             }
328             // Get the element name without extensions by calling stem() twice
329             // since stem() returns the base name if no periods are found.
330             // Therefore both "element.P10" and "element.P10.iplTime" would
331             // become "element".
332             attr[path.stem().stem()] = lid;
333             continue;
334         }
335 
336         // Process all other extensions. The extension should match the list of
337         // supported extensions for this system. Use .insert() to only add
338         // entries that do not exist, so to not overwrite the values that may
339         // had been added that had the ipl extension.
340         if (std::find(extensions.begin(), extensions.end(), path.extension()) !=
341             extensions.end())
342         {
343             attr.insert({path.stem(), lid});
344         }
345     }
346     for (const auto& a : attr)
347     {
348         // Build the bios attribute string with format:
349         // "element1=lid1,element2=lid2,elementN=lidN,"
350         biosAttrStr += a.first + "=" + a.second + ",";
351 
352         // Create symlinks from the hostfw elements to their corresponding
353         // lid files if they don't exist
354         auto elementFilePath =
355             std::filesystem::path("/media/hostfw/running") / a.first;
356         if (!std::filesystem::exists(elementFilePath))
357         {
358             std::error_code ec;
359             auto lidName = a.second + ".lid";
360             std::filesystem::create_symlink(lidName, elementFilePath, ec);
361             if (ec)
362             {
363                 log<level::ERR>("Error creating symlink",
364                                 entry("TARGET=%s", lidName.c_str()),
365                                 entry("LINK=%s", elementFilePath.c_str()));
366             }
367         }
368     }
369 
370     // Delete the last comma of the bios attribute string
371     if (biosAttrStr.back() == ',')
372     {
373         return biosAttrStr.substr(0, biosAttrStr.length() - 1);
374     }
375 
376     return biosAttrStr;
377 }
378 
379 /**
380  * @brief Set the bios attribute table with details of the host firmware data
381  * for this system.
382  *
383  * @param[in] elementsJsonFilePath - The path to the host firmware json file.
384  * @param[in] extentions - The extensions of the firmware blob files.
385  */
386 void setBiosAttr(const std::filesystem::path& elementsJsonFilePath,
387                  const std::vector<std::string>& extensions)
388 {
389     auto biosAttrStr = getBiosAttrStr(elementsJsonFilePath, extensions);
390 
391     constexpr auto biosConfigPath = "/xyz/openbmc_project/bios_config/manager";
392     constexpr auto biosConfigIntf = "xyz.openbmc_project.BIOSConfig.Manager";
393     constexpr auto dbusAttrName = "hb_lid_ids";
394     constexpr auto dbusAttrType =
395         "xyz.openbmc_project.BIOSConfig.Manager.AttributeType.String";
396 
397     using PendingAttributesType = std::vector<std::pair<
398         std::string, std::tuple<std::string, std::variant<std::string>>>>;
399     PendingAttributesType pendingAttributes;
400     pendingAttributes.emplace_back(std::make_pair(
401         dbusAttrName, std::make_tuple(dbusAttrType, biosAttrStr)));
402 
403     auto bus = sdbusplus::bus::new_default();
404     auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
405                                       MAPPER_INTERFACE, "GetObject");
406     method.append(biosConfigPath, std::vector<std::string>({biosConfigIntf}));
407     std::vector<std::pair<std::string, std::vector<std::string>>> response;
408     try
409     {
410         auto reply = bus.call(method);
411         reply.read(response);
412         if (response.empty())
413         {
414             log<level::ERR>("Error reading mapper response",
415                             entry("PATH=%s", biosConfigPath),
416                             entry("INTERFACE=%s", biosConfigIntf));
417             return;
418         }
419         auto method = bus.new_method_call((response.begin()->first).c_str(),
420                                           biosConfigPath,
421                                           SYSTEMD_PROPERTY_INTERFACE, "Set");
422         method.append(biosConfigIntf, "PendingAttributes",
423                       std::variant<PendingAttributesType>(pendingAttributes));
424         bus.call(method);
425     }
426     catch (const sdbusplus::exception::exception& e)
427     {
428         log<level::ERR>("Error setting the bios attribute",
429                         entry("ERROR=%s", e.what()),
430                         entry("ATTRIBUTE=%s", dbusAttrName));
431         return;
432     }
433 }
434 
435 /**
436  * @brief Make callbacks on
437  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
438  *
439  * Look for an instance of
440  * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
441  * argument and if found, issue the provided callback.
442  *
443  * @param[in] interfacesAndProperties the interfaces in which to look for an
444  * instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem
445  * @param[in] callback the user callback to make if
446  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
447  * interfacesAndProperties
448  * @return true if interfacesAndProperties contained an instance of
449  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
450  */
451 bool maybeCall(const std::map<std::string,
452                               std::map<std::string,
453                                        std::variant<std::vector<std::string>>>>&
454                    interfacesAndProperties,
455                const MaybeCallCallbackType& callback)
456 {
457     using namespace std::string_literals;
458 
459     static const auto interfaceName =
460         "xyz.openbmc_project.Configuration.IBMCompatibleSystem"s;
461     auto interfaceIterator = interfacesAndProperties.find(interfaceName);
462     if (interfaceIterator == interfacesAndProperties.cend())
463     {
464         // IBMCompatibleSystem interface not found, so instruct the caller to
465         // keep waiting or try again later.
466         return false;
467     }
468     auto propertyIterator = interfaceIterator->second.find("Names"s);
469     if (propertyIterator == interfaceIterator->second.cend())
470     {
471         // The interface exists but the property doesn't.  This is a bug in the
472         // IBMCompatibleSystem implementation.  The caller should not try
473         // again.
474         std::cerr << "Names property not implemented on " << interfaceName
475                   << "\n";
476         return true;
477     }
478 
479     const auto& ibmCompatibleSystem =
480         std::get<std::vector<std::string>>(propertyIterator->second);
481     if (callback)
482     {
483         callback(ibmCompatibleSystem);
484     }
485 
486     // IBMCompatibleSystem found and callback issued.
487     return true;
488 }
489 
490 /**
491  * @brief Make callbacks on
492  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
493  *
494  * Look for an instance of
495  * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
496  * argument and if found, issue the provided callback.
497  *
498  * @param[in] message the DBus message in which to look for an instance of
499  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
500  * @param[in] callback the user callback to make if
501  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
502  * message
503  * @return true if message contained an instance of
504  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
505  */
506 bool maybeCallMessage(sdbusplus::message::message& message,
507                       const MaybeCallCallbackType& callback)
508 {
509     std::map<std::string,
510              std::map<std::string, std::variant<std::vector<std::string>>>>
511         interfacesAndProperties;
512     sdbusplus::message::object_path _;
513     message.read(_, interfacesAndProperties);
514     return maybeCall(interfacesAndProperties, callback);
515 }
516 
517 /**
518  * @brief Determine system support for host firmware well-known names.
519  *
520  * Using the provided extensionMap and
521  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if
522  * well-known names for host firmare blob files are necessary and if so, create
523  * them.
524  *
525  * @param[in] extensionMap a map of
526  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
527  * file extensions.
528  * @param[in] hostFirmwareDirectory The directory in which findLinks should
529  * look for host firmware blob files that need well-known names.
530  * @param[in] ibmCompatibleSystem The names property of an instance of
531  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
532  * @param[in] errorCallback A callback made in the event of filesystem errors.
533  */
534 void maybeMakeLinks(
535     const std::map<std::string, std::vector<std::string>>& extensionMap,
536     const std::filesystem::path& hostFirmwareDirectory,
537     const std::vector<std::string>& ibmCompatibleSystem,
538     const ErrorCallbackType& errorCallback)
539 {
540     std::vector<std::string> extensions;
541     if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
542                                             extensions))
543     {
544         findLinks(hostFirmwareDirectory, extensions, errorCallback, writeLink);
545     }
546 }
547 
548 /**
549  * @brief Determine system support for updating the bios attribute table.
550  *
551  * Using the provided extensionMap and
552  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if the bios
553  * attribute table needs to be updated.
554  *
555  * @param[in] extensionMap a map of
556  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
557  * file extensions.
558  * @param[in] elementsJsonFilePath The file path to the json file
559  * @param[in] ibmCompatibleSystem The names property of an instance of
560  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
561  */
562 void maybeSetBiosAttr(
563     const std::map<std::string, std::vector<std::string>>& extensionMap,
564     const std::filesystem::path& elementsJsonFilePath,
565     const std::vector<std::string>& ibmCompatibleSystem)
566 {
567     std::vector<std::string> extensions;
568     if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
569                                             extensions))
570     {
571         setBiosAttr(elementsJsonFilePath, extensions);
572     }
573 }
574 
575 /**
576  * @brief process host firmware
577  *
578  * Allocate a callback context and register for DBus.ObjectManager Interfaces
579  * added signals from entity manager.
580  *
581  * Check the current entity manager object tree for a
582  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instance (entity
583  * manager will be dbus activated if it is not running).  If one is found,
584  * determine if symlinks need to be created and create them.  Instruct the
585  * program event loop to exit.
586  *
587  * If no instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
588  * found return the callback context to main, where the program will sleep
589  * until the callback is invoked one or more times and instructs the program
590  * event loop to exit when
591  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is added.
592  *
593  * @param[in] bus a DBus client connection
594  * @param[in] extensionMap a map of
595  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
596  * file extensions.
597  * @param[in] hostFirmwareDirectory The directory in which processHostFirmware
598  * should look for blob files.
599  * @param[in] errorCallback A callback made in the event of filesystem errors.
600  * @param[in] loop a program event loop
601  * @return nullptr if an instance of
602  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found, otherwise a
603  * pointer to an sdbusplus match object.
604  */
605 std::shared_ptr<void> processHostFirmware(
606     sdbusplus::bus::bus& bus,
607     std::map<std::string, std::vector<std::string>> extensionMap,
608     std::filesystem::path hostFirmwareDirectory,
609     ErrorCallbackType errorCallback, sdeventplus::Event& loop)
610 {
611     // ownership of extensionMap, hostFirmwareDirectory and errorCallback can't
612     // be transfered to the match callback because they are needed in the non
613     // async part of this function below, so they need to be moved to the heap.
614     auto pExtensionMap =
615         std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
616     auto pHostFirmwareDirectory =
617         std::make_shared<decltype(hostFirmwareDirectory)>(
618             std::move(hostFirmwareDirectory));
619     auto pErrorCallback =
620         std::make_shared<decltype(errorCallback)>(std::move(errorCallback));
621 
622     // register for a callback in case the IBMCompatibleSystem interface has
623     // not yet been published by entity manager.
624     auto interfacesAddedMatch = std::make_shared<sdbusplus::bus::match::match>(
625         bus,
626         sdbusplus::bus::match::rules::interfacesAdded() +
627             sdbusplus::bus::match::rules::sender(
628                 "xyz.openbmc_project.EntityManager"),
629         [pExtensionMap, pHostFirmwareDirectory, pErrorCallback,
630          &loop](auto& message) {
631             // bind the extension map, host firmware directory, and error
632             // callback to the maybeMakeLinks function.
633             auto maybeMakeLinksWithArgsBound =
634                 std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
635                           std::cref(*pHostFirmwareDirectory),
636                           std::placeholders::_1, std::cref(*pErrorCallback));
637 
638             // if the InterfacesAdded message contains an an instance of
639             // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to
640             // see if links are necessary on this system and if so, create
641             // them.
642             if (maybeCallMessage(message, maybeMakeLinksWithArgsBound))
643             {
644                 // The IBMCompatibleSystem interface was found and the links
645                 // were created if applicable.  Instruct the event loop /
646                 // subcommand to exit.
647                 loop.exit(0);
648             }
649         });
650 
651     // now that we'll get a callback in the event of an InterfacesAdded signal
652     // (potentially containing
653     // xyz.openbmc_project.Configuration.IBMCompatibleSystem), activate entity
654     // manager if it isn't running and enumerate its objects
655     auto getManagedObjects = bus.new_method_call(
656         "xyz.openbmc_project.EntityManager", "/",
657         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
658     std::map<std::string,
659              std::map<std::string, std::variant<std::vector<std::string>>>>
660         interfacesAndProperties;
661     std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)>
662         objects;
663     try
664     {
665         auto reply = bus.call(getManagedObjects);
666         reply.read(objects);
667     }
668     catch (const sdbusplus::exception::exception& e)
669     {
670         // Error querying the EntityManager interface. Return the match to have
671         // the callback run if/when the interface appears in D-Bus.
672         return interfacesAddedMatch;
673     }
674 
675     // bind the extension map, host firmware directory, and error callback to
676     // the maybeMakeLinks function.
677     auto maybeMakeLinksWithArgsBound =
678         std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
679                   std::cref(*pHostFirmwareDirectory), std::placeholders::_1,
680                   std::cref(*pErrorCallback));
681 
682     for (const auto& pair : objects)
683     {
684         std::tie(std::ignore, interfacesAndProperties) = pair;
685         // if interfacesAndProperties contains an an instance of
686         // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to see
687         // if links are necessary on this system and if so, create them
688         if (maybeCall(interfacesAndProperties, maybeMakeLinksWithArgsBound))
689         {
690             // The IBMCompatibleSystem interface is already on the bus and the
691             // links were created if applicable.  Instruct the event loop to
692             // exit.
693             loop.exit(0);
694             // The match object isn't needed anymore, so destroy it on return.
695             return nullptr;
696         }
697     }
698 
699     // The IBMCompatibleSystem interface has not yet been published.  Move
700     // ownership of the match callback to the caller.
701     return interfacesAddedMatch;
702 }
703 
704 /**
705  * @brief Update the Bios Attribute Table
706  *
707  * If an instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
708  * found, update the Bios Attribute Table with the appropriate host firmware
709  * data.
710  *
711  * @param[in] bus - D-Bus client connection.
712  * @param[in] extensionMap - Map of IBMCompatibleSystem names and host firmware
713  *                           file extensions.
714  * @param[in] elementsJsonFilePath - The Path to the json file
715  * @param[in] loop - Program event loop.
716  * @return nullptr
717  */
718 std::vector<std::shared_ptr<void>> updateBiosAttrTable(
719     sdbusplus::bus::bus& bus,
720     std::map<std::string, std::vector<std::string>> extensionMap,
721     std::filesystem::path elementsJsonFilePath, sdeventplus::Event& loop)
722 {
723     constexpr auto pldmPath = "/xyz/openbmc_project/pldm";
724     constexpr auto entityManagerServiceName =
725         "xyz.openbmc_project.EntityManager";
726 
727     auto pExtensionMap =
728         std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
729     auto pElementsJsonFilePath =
730         std::make_shared<decltype(elementsJsonFilePath)>(
731             std::move(elementsJsonFilePath));
732 
733     // Entity Manager is needed to get the list of supported extensions. Add a
734     // match to monitor interfaces added in case it's not running yet.
735     auto maybeSetAttrWithArgsBound =
736         std::bind(maybeSetBiosAttr, std::cref(*pExtensionMap),
737                   std::cref(*pElementsJsonFilePath), std::placeholders::_1);
738 
739     std::vector<std::shared_ptr<void>> matches;
740     matches.emplace_back(std::make_shared<sdbusplus::bus::match::match>(
741         bus,
742         sdbusplus::bus::match::rules::interfacesAdded() +
743             sdbusplus::bus::match::rules::sender(
744                 "xyz.openbmc_project.EntityManager"),
745         [pldmPath, pExtensionMap, pElementsJsonFilePath,
746          maybeSetAttrWithArgsBound, &loop](auto& message) {
747             auto bus = sdbusplus::bus::new_default();
748             auto pldmObject = getObject(bus, pldmPath);
749             if (pldmObject.empty())
750             {
751                 return;
752             }
753             if (maybeCallMessage(message, maybeSetAttrWithArgsBound))
754             {
755                 loop.exit(0);
756             }
757         }));
758     matches.emplace_back(std::make_shared<sdbusplus::bus::match::match>(
759         bus,
760         sdbusplus::bus::match::rules::nameOwnerChanged() +
761             sdbusplus::bus::match::rules::arg0namespace(
762                 "xyz.openbmc_project.PLDM"),
763         [pExtensionMap, pElementsJsonFilePath, maybeSetAttrWithArgsBound,
764          &loop](auto& message) {
765             std::string name;
766             std::string oldOwner;
767             std::string newOwner;
768             message.read(name, oldOwner, newOwner);
769 
770             if (newOwner.empty())
771             {
772                 return;
773             }
774 
775             auto bus = sdbusplus::bus::new_default();
776             InterfacesPropertiesMap interfacesAndProperties;
777             auto objects = getManagedObjects(bus, entityManagerServiceName);
778             for (const auto& pair : objects)
779             {
780                 std::tie(std::ignore, interfacesAndProperties) = pair;
781                 if (maybeCall(interfacesAndProperties,
782                               maybeSetAttrWithArgsBound))
783                 {
784                     loop.exit(0);
785                 }
786             }
787         }));
788 
789     // The BIOS attribute table can only be updated if PLDM is running because
790     // PLDM is the one that exposes this property. Return if it's not running.
791     auto pldmObject = getObject(bus, pldmPath);
792     if (pldmObject.empty())
793     {
794         return matches;
795     }
796 
797     InterfacesPropertiesMap interfacesAndProperties;
798     auto objects = getManagedObjects(bus, entityManagerServiceName);
799     for (const auto& pair : objects)
800     {
801         std::tie(std::ignore, interfacesAndProperties) = pair;
802         if (maybeCall(interfacesAndProperties, maybeSetAttrWithArgsBound))
803         {
804             loop.exit(0);
805             return {};
806         }
807     }
808 
809     return matches;
810 }
811 
812 } // namespace process_hostfirmware
813 } // namespace functions
814