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