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