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