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 Compatible
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.Inventory.Decorator.Compatible are packaged with
92  * different filename extensions. getExtensionsForIbmCompatibleSystem maintains
93  * the mapping from a given value of
94  * xyz.openbmc_project.Inventory.Decorator.Compatible to an array of filename
95  * 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 found
99  * getExtensionsForIbmCompatibleSystem returns false and extensions is
100  * unmodified.
101  *
102  * @param[in] extensionMap a map of
103  * xyz.openbmc_project.Inventory.Decorator.Compatible to host firmware blob file
104  * extensions.
105  * @param[in] ibmCompatibleSystem The names property of an instance of
106  * xyz.openbmc_project.Inventory.Decorator.Compatible
107  * @param[out] extensions 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  * bootstrapping 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] extensions 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(
188         hostFirmwareDirectory, 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 existing 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 =
352             std::filesystem::path("/media/hostfw/running") / 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] extensions - 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.Inventory.Decorator.Compatible instances.
436  *
437  * Look for an instance of xyz.openbmc_project.Inventory.Decorator.Compatible in
438  * the provided argument and if found, issue the provided callback.
439  *
440  * @param[in] interfacesAndProperties the interfaces in which to look for an
441  * instance of xyz.openbmc_project.Inventory.Decorator.Compatible
442  * @param[in] callback the user callback to make if
443  * xyz.openbmc_project.Inventory.Decorator.Compatible is found in
444  * interfacesAndProperties
445  * @return true if interfacesAndProperties contained an instance of
446  * xyz.openbmc_project.Inventory.Decorator.Compatible, false otherwise
447  */
448 bool maybeCall(
449     const std::map<
450         std::string,
451         std::map<std::string, 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.Inventory.Decorator.Compatible"s;
459     auto interfaceIterator = interfacesAndProperties.find(interfaceName);
460     if (interfaceIterator == interfacesAndProperties.cend())
461     {
462         // Compatible interface not found, so instruct the caller to keep
463         // 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         // Compatible implementation. The caller should not try again.
471         std::cerr << "Names property not implemented on " << interfaceName
472                   << "\n";
473         return true;
474     }
475 
476     const auto& ibmCompatibleSystem =
477         std::get<std::vector<std::string>>(propertyIterator->second);
478     if (callback)
479     {
480         try
481         {
482             callback(ibmCompatibleSystem);
483         }
484         catch (const sdbusplus::exception_t& e)
485         {
486             return false;
487         }
488     }
489 
490     // Compatible found and callback issued.
491     return true;
492 }
493 
494 /**
495  * @brief Make callbacks on
496  * xyz.openbmc_project.Inventory.Decorator.Compatible instances.
497  *
498  * Look for an instance ofxyz.openbmc_project.Inventory.Decorator.Compatible in
499  * the provided 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.Inventory.Decorator.Compatible
503  * @param[in] callback the user callback to make if
504  * xyz.openbmc_project.Inventory.Decorator.Compatible is found in message
505  * @return true if message contained an instance of
506  * xyz.openbmc_project.Inventory.Decorator.Compatible, false otherwise
507  */
508 bool maybeCallMessage(sdbusplus::message_t& message,
509                       const MaybeCallCallbackType& callback)
510 {
511     std::map<std::string,
512              std::map<std::string, std::variant<std::vector<std::string>>>>
513         interfacesAndProperties;
514     sdbusplus::message::object_path _;
515     message.read(_, interfacesAndProperties);
516     return maybeCall(interfacesAndProperties, callback);
517 }
518 
519 /**
520  * @brief Determine system support for host firmware well-known names.
521  *
522  * Using the provided extensionMap and
523  * xyz.openbmc_project.Inventory.Decorator.Compatible, determine if well-known
524  * names for host firmware blob files are necessary and if so, create them.
525  *
526  * @param[in] extensionMap a map of
527  * xyz.openbmc_project.Inventory.Decorator.Compatible to host firmware blob file
528  * extensions.
529  * @param[in] hostFirmwareDirectory The directory in which findLinks should look
530  * for host firmware blob files that need well-known names.
531  * @param[in] ibmCompatibleSystem The names property of an instance of
532  * xyz.openbmc_project.Inventory.Decorator.Compatible
533  * @param[in] errorCallback A callback made in the event of filesystem errors.
534  */
535 void maybeMakeLinks(
536     const std::map<std::string, std::vector<std::string>>& extensionMap,
537     const std::filesystem::path& hostFirmwareDirectory,
538     const std::vector<std::string>& ibmCompatibleSystem,
539     const ErrorCallbackType& errorCallback)
540 {
541     std::vector<std::string> extensions;
542     if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
543                                             extensions))
544     {
545         findLinks(hostFirmwareDirectory, extensions, errorCallback, writeLink);
546     }
547 }
548 
549 /**
550  * @brief Determine system support for updating the bios attribute table.
551  *
552  * Using the provided extensionMap and
553  * xyz.openbmc_project.Inventory.Decorator.Compatible, determine if the bios
554  * attribute table needs to be updated.
555  *
556  * @param[in] extensionMap a map of
557  * xyz.openbmc_project.Inventory.Decorator.Compatible to host firmware blob file
558  * extensions.
559  * @param[in] elementsJsonFilePath The file path to the json file
560  * @param[in] ibmCompatibleSystem The names property of an instance of
561  * xyz.openbmc_project.Inventory.Decorator.Compatible
562  */
563 void maybeSetBiosAttr(
564     const std::map<std::string, std::vector<std::string>>& extensionMap,
565     const std::filesystem::path& elementsJsonFilePath,
566     const std::vector<std::string>& ibmCompatibleSystem)
567 {
568     std::vector<std::string> extensions;
569     if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
570                                             extensions))
571     {
572         try
573         {
574             setBiosAttr(elementsJsonFilePath, extensions);
575         }
576         catch (const sdbusplus::exception_t& e)
577         {
578             throw;
579         }
580     }
581 }
582 
583 /**
584  * @brief process host firmware
585  *
586  * Allocate a callback context and register for DBus.ObjectManager Interfaces
587  * added signals from entity manager.
588  *
589  * Check the current entity manager object tree for a
590  * xyz.openbmc_project.Inventory.Decorator.Compatible instance (entity manager
591  * will be dbus activated if it is not running). If one is found, determine if
592  * symlinks need to be created and create them. Instruct the program event loop
593  * to exit.
594  *
595  * If no instance of xyz.openbmc_project.Inventory.Decorator.Compatible is found
596  * return the callback context to main, where the program will sleep until the
597  * callback is invoked one or more times and instructs the program event loop to
598  * exit when xyz.openbmc_project.Inventory.Decorator.Compatible is added.
599  *
600  * @param[in] bus a DBus client connection
601  * @param[in] extensionMap a map of
602  * xyz.openbmc_project.Inventory.Decorator.Compatible to host firmware blob file
603  * extensions.
604  * @param[in] hostFirmwareDirectory The directory in which processHostFirmware
605  * should look for blob files.
606  * @param[in] errorCallback A callback made in the event of filesystem errors.
607  * @param[in] loop a program event loop
608  * @return nullptr if an instance of
609  * xyz.openbmc_project.Inventory.Decorator.Compatible is found, otherwise a
610  * pointer to an sdbusplus match object.
611  */
612 std::shared_ptr<void> processHostFirmware(
613     sdbusplus::bus_t& bus,
614     std::map<std::string, std::vector<std::string>> extensionMap,
615     std::filesystem::path hostFirmwareDirectory,
616     ErrorCallbackType errorCallback, sdeventplus::Event& loop)
617 {
618     // ownership of extensionMap, hostFirmwareDirectory and errorCallback can't
619     // be transferred to the match callback because they are needed in the non
620     // async part of this function below, so they need to be moved to the heap.
621     auto pExtensionMap =
622         std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
623     auto pHostFirmwareDirectory =
624         std::make_shared<decltype(hostFirmwareDirectory)>(
625             std::move(hostFirmwareDirectory));
626     auto pErrorCallback =
627         std::make_shared<decltype(errorCallback)>(std::move(errorCallback));
628 
629     // register for a callback in case the Compatible interface has not yet been
630     // published by entity manager.
631     auto interfacesAddedMatch = std::make_shared<sdbusplus::bus::match_t>(
632         bus,
633         sdbusplus::bus::match::rules::interfacesAdded() +
634             sdbusplus::bus::match::rules::sender(
635                 "xyz.openbmc_project.EntityManager"),
636         [pExtensionMap, pHostFirmwareDirectory, pErrorCallback,
637          &loop](auto& message) {
638             // bind the extension map, host firmware directory, and error
639             // callback to the maybeMakeLinks function.
640             auto maybeMakeLinksWithArgsBound =
641                 std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
642                           std::cref(*pHostFirmwareDirectory),
643                           std::placeholders::_1, std::cref(*pErrorCallback));
644 
645             // if the InterfacesAdded message contains an an instance of
646             // xyz.openbmc_project.Inventory.Decorator.Compatible, check to see
647             // if links are necessary on this system and if so, create them.
648             if (maybeCallMessage(message, maybeMakeLinksWithArgsBound))
649             {
650                 // The Compatible interface was found and the links were created
651                 // if applicable. Instruct the event loop / subcommand to exit.
652                 loop.exit(0);
653             }
654         });
655 
656     // now that we'll get a callback in the event of an InterfacesAdded signal
657     // (potentially containing
658     // xyz.openbmc_project.Inventory.Decorator.Compatible), activate entity
659     // manager if it isn't running and enumerate its objects
660     auto getManagedObjects = bus.new_method_call(
661         "xyz.openbmc_project.EntityManager", "/xyz/openbmc_project/inventory",
662         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
663     std::map<std::string,
664              std::map<std::string, std::variant<std::vector<std::string>>>>
665         interfacesAndProperties;
666     std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)>
667         objects;
668     try
669     {
670         auto reply = bus.call(getManagedObjects);
671         reply.read(objects);
672     }
673     catch (const sdbusplus::exception_t& e)
674     {
675         // Error querying the EntityManager interface. Return the match to have
676         // the callback run if/when the interface appears in D-Bus.
677         return interfacesAddedMatch;
678     }
679 
680     // bind the extension map, host firmware directory, and error callback to
681     // the maybeMakeLinks function.
682     auto maybeMakeLinksWithArgsBound =
683         std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
684                   std::cref(*pHostFirmwareDirectory), std::placeholders::_1,
685                   std::cref(*pErrorCallback));
686 
687     for (const auto& pair : objects)
688     {
689         std::tie(std::ignore, interfacesAndProperties) = pair;
690         // if interfacesAndProperties contains an an instance of
691         // xyz.openbmc_project.Inventory.Decorator.Compatible, check to see if
692         // links are necessary on this system and if so, create them
693         if (maybeCall(interfacesAndProperties, maybeMakeLinksWithArgsBound))
694         {
695             // The Compatible interface is already on the bus and the links were
696             // created if applicable. Instruct the event loop to exit.
697             loop.exit(0);
698             // The match object isn't needed anymore, so destroy it on return.
699             return nullptr;
700         }
701     }
702 
703     // The Compatible interface has not yet been published. Move ownership of
704     // the match callback to the caller.
705     return interfacesAddedMatch;
706 }
707 
708 /**
709  * @brief Update the Bios Attribute Table
710  *
711  * If an instance of xyz.openbmc_project.Inventory.Decorator.Compatible is
712  * found, update the Bios Attribute Table with the appropriate host firmware
713  * data.
714  *
715  * @param[in] bus - D-Bus client connection.
716  * @param[in] extensionMap - Map of Compatible names and host firmware file
717                              extensions.
718  * @param[in] elementsJsonFilePath - The Path to the json file
719  * @param[in] loop - Program event loop.
720  * @return nullptr
721  */
722 std::vector<std::shared_ptr<void>> updateBiosAttrTable(
723     sdbusplus::bus_t& bus,
724     std::map<std::string, std::vector<std::string>> extensionMap,
725     std::filesystem::path elementsJsonFilePath, sdeventplus::Event& loop)
726 {
727     constexpr auto pldmPath = "/xyz/openbmc_project/pldm";
728     constexpr auto entityManagerServiceName =
729         "xyz.openbmc_project.EntityManager";
730 
731     auto pExtensionMap =
732         std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
733     auto pElementsJsonFilePath =
734         std::make_shared<decltype(elementsJsonFilePath)>(
735             std::move(elementsJsonFilePath));
736 
737     auto maybeSetAttrWithArgsBound =
738         std::bind(maybeSetBiosAttr, std::cref(*pExtensionMap),
739                   std::cref(*pElementsJsonFilePath), std::placeholders::_1);
740 
741     std::vector<std::shared_ptr<void>> matches;
742 
743     // Entity Manager is needed to get the list of supported extensions. Add a
744     // match to monitor interfaces added in case it's not running yet.
745     matches.emplace_back(std::make_shared<sdbusplus::bus::match_t>(
746         bus,
747         sdbusplus::bus::match::rules::interfacesAdded() +
748             sdbusplus::bus::match::rules::sender(
749                 "xyz.openbmc_project.EntityManager"),
750         [pldmPath, pExtensionMap, pElementsJsonFilePath,
751          maybeSetAttrWithArgsBound, &loop](auto& message) {
752             if (maybeCallMessage(message, maybeSetAttrWithArgsBound))
753             {
754                 loop.exit(0);
755             }
756         }));
757 
758     // The BIOS attribute table can only be updated if PLDM is running because
759     // PLDM is the one that exposes this property. Add a match to monitor when
760     // the PLDM service starts.
761     matches.emplace_back(std::make_shared<sdbusplus::bus::match_t>(
762         bus,
763         sdbusplus::bus::match::rules::nameOwnerChanged() +
764             sdbusplus::bus::match::rules::arg0namespace(
765                 "xyz.openbmc_project.PLDM"),
766         [pExtensionMap, pElementsJsonFilePath, maybeSetAttrWithArgsBound,
767          &loop](auto& message) {
768             std::string name;
769             std::string oldOwner;
770             std::string newOwner;
771             message.read(name, oldOwner, newOwner);
772 
773             if (newOwner.empty())
774             {
775                 return;
776             }
777 
778             auto bus = sdbusplus::bus::new_default();
779             InterfacesPropertiesMap interfacesAndProperties;
780             auto objects = getManagedObjects(bus, entityManagerServiceName,
781                                              "/xyz/openbmc_project/inventory");
782             for (const auto& pair : objects)
783             {
784                 std::tie(std::ignore, interfacesAndProperties) = pair;
785                 if (maybeCall(interfacesAndProperties,
786                               maybeSetAttrWithArgsBound))
787                 {
788                     loop.exit(0);
789                 }
790             }
791         }));
792 
793     InterfacesPropertiesMap interfacesAndProperties;
794     auto objects = getManagedObjects(bus, entityManagerServiceName,
795                                      "/xyz/openbmc_project/inventory");
796     for (const auto& pair : objects)
797     {
798         std::tie(std::ignore, interfacesAndProperties) = pair;
799         if (maybeCall(interfacesAndProperties, maybeSetAttrWithArgsBound))
800         {
801             loop.exit(0);
802             return {};
803         }
804     }
805 
806     return matches;
807 }
808 
809 } // namespace process_hostfirmware
810 } // namespace functions
811