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::SdBusError& 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::SdBusError& 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 from HBB to the corresponding LID file if it exists
224     static const auto hbbLid = "81e0065a.lid";
225     auto hbbLidPath = hostFirmwareDirectory / hbbLid;
226     if (std::filesystem::exists(hbbLidPath))
227     {
228         static const auto hbbName = "HBB";
229         auto hbbLinkPath = hostFirmwareDirectory / hbbName;
230         makeCallback(linkCallback, hbbLid, hbbLinkPath, 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 
353     return biosAttrStr;
354 }
355 
356 /**
357  * @brief Set the bios attribute table with details of the host firmware data
358  * for this system.
359  *
360  * @param[in] elementsJsonFilePath - The path to the host firmware json file.
361  * @param[in] extentions - The extensions of the firmware blob files.
362  */
363 void setBiosAttr(const std::filesystem::path& elementsJsonFilePath,
364                  const std::vector<std::string>& extensions)
365 {
366     auto biosAttrStr = getBiosAttrStr(elementsJsonFilePath, extensions);
367 
368     constexpr auto biosConfigPath = "/xyz/openbmc_project/bios_config/manager";
369     constexpr auto biosConfigIntf = "xyz.openbmc_project.BIOSConfig.Manager";
370     constexpr auto dbusAttrName = "hb_lid_ids";
371     constexpr auto dbusAttrType =
372         "xyz.openbmc_project.BIOSConfig.Manager.AttributeType.String";
373 
374     using PendingAttributesType = std::vector<std::pair<
375         std::string, std::tuple<std::string, std::variant<std::string>>>>;
376     PendingAttributesType pendingAttributes;
377     pendingAttributes.emplace_back(std::make_pair(
378         dbusAttrName, std::make_tuple(dbusAttrType, biosAttrStr)));
379 
380     auto bus = sdbusplus::bus::new_default();
381     auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
382                                       MAPPER_INTERFACE, "GetObject");
383     method.append(biosConfigPath, std::vector<std::string>({biosConfigIntf}));
384     std::vector<std::pair<std::string, std::vector<std::string>>> response;
385     try
386     {
387         auto reply = bus.call(method);
388         reply.read(response);
389         if (response.empty())
390         {
391             log<level::ERR>("Error reading mapper response",
392                             entry("PATH=%s", biosConfigPath),
393                             entry("INTERFACE=%s", biosConfigIntf));
394             return;
395         }
396         auto method = bus.new_method_call((response.begin()->first).c_str(),
397                                           biosConfigPath,
398                                           SYSTEMD_PROPERTY_INTERFACE, "Set");
399         method.append(biosConfigIntf, "PendingAttributes",
400                       std::variant<PendingAttributesType>(pendingAttributes));
401         bus.call(method);
402     }
403     catch (const sdbusplus::exception::SdBusError& e)
404     {
405         log<level::ERR>("Error setting the bios attribute",
406                         entry("ERROR=%s", e.what()),
407                         entry("ATTRIBUTE=%s", dbusAttrName));
408         return;
409     }
410 }
411 
412 /**
413  * @brief Make callbacks on
414  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
415  *
416  * Look for an instance of
417  * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
418  * argument and if found, issue the provided callback.
419  *
420  * @param[in] interfacesAndProperties the interfaces in which to look for an
421  * instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem
422  * @param[in] callback the user callback to make if
423  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
424  * interfacesAndProperties
425  * @return true if interfacesAndProperties contained an instance of
426  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
427  */
428 bool maybeCall(const std::map<std::string,
429                               std::map<std::string,
430                                        std::variant<std::vector<std::string>>>>&
431                    interfacesAndProperties,
432                const MaybeCallCallbackType& callback)
433 {
434     using namespace std::string_literals;
435 
436     static const auto interfaceName =
437         "xyz.openbmc_project.Configuration.IBMCompatibleSystem"s;
438     auto interfaceIterator = interfacesAndProperties.find(interfaceName);
439     if (interfaceIterator == interfacesAndProperties.cend())
440     {
441         // IBMCompatibleSystem interface not found, so instruct the caller to
442         // keep waiting or try again later.
443         return false;
444     }
445     auto propertyIterator = interfaceIterator->second.find("Names"s);
446     if (propertyIterator == interfaceIterator->second.cend())
447     {
448         // The interface exists but the property doesn't.  This is a bug in the
449         // IBMCompatibleSystem implementation.  The caller should not try
450         // again.
451         std::cerr << "Names property not implemented on " << interfaceName
452                   << "\n";
453         return true;
454     }
455 
456     const auto& ibmCompatibleSystem =
457         std::get<std::vector<std::string>>(propertyIterator->second);
458     if (callback)
459     {
460         callback(ibmCompatibleSystem);
461     }
462 
463     // IBMCompatibleSystem found and callback issued.
464     return true;
465 }
466 
467 /**
468  * @brief Make callbacks on
469  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
470  *
471  * Look for an instance of
472  * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
473  * argument and if found, issue the provided callback.
474  *
475  * @param[in] message the DBus message in which to look for an instance of
476  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
477  * @param[in] callback the user callback to make if
478  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
479  * message
480  * @return true if message contained an instance of
481  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
482  */
483 bool maybeCallMessage(sdbusplus::message::message& message,
484                       const MaybeCallCallbackType& callback)
485 {
486     std::map<std::string,
487              std::map<std::string, std::variant<std::vector<std::string>>>>
488         interfacesAndProperties;
489     sdbusplus::message::object_path _;
490     message.read(_, interfacesAndProperties);
491     return maybeCall(interfacesAndProperties, callback);
492 }
493 
494 /**
495  * @brief Determine system support for host firmware well-known names.
496  *
497  * Using the provided extensionMap and
498  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if
499  * well-known names for host firmare blob files are necessary and if so, create
500  * them.
501  *
502  * @param[in] extensionMap a map of
503  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
504  * file extensions.
505  * @param[in] hostFirmwareDirectory The directory in which findLinks should
506  * look for host firmware blob files that need well-known names.
507  * @param[in] ibmCompatibleSystem The names property of an instance of
508  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
509  * @param[in] errorCallback A callback made in the event of filesystem errors.
510  */
511 void maybeMakeLinks(
512     const std::map<std::string, std::vector<std::string>>& extensionMap,
513     const std::filesystem::path& hostFirmwareDirectory,
514     const std::vector<std::string>& ibmCompatibleSystem,
515     const ErrorCallbackType& errorCallback)
516 {
517     std::vector<std::string> extensions;
518     if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
519                                             extensions))
520     {
521         findLinks(hostFirmwareDirectory, extensions, errorCallback, writeLink);
522     }
523 }
524 
525 /**
526  * @brief Determine system support for updating the bios attribute table.
527  *
528  * Using the provided extensionMap and
529  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if the bios
530  * attribute table needs to be updated.
531  *
532  * @param[in] extensionMap a map of
533  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
534  * file extensions.
535  * @param[in] elementsJsonFilePath The file path to the json file
536  * @param[in] ibmCompatibleSystem The names property of an instance of
537  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
538  */
539 void maybeSetBiosAttr(
540     const std::map<std::string, std::vector<std::string>>& extensionMap,
541     const std::filesystem::path& elementsJsonFilePath,
542     const std::vector<std::string>& ibmCompatibleSystem)
543 {
544     std::vector<std::string> extensions;
545     if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
546                                             extensions))
547     {
548         setBiosAttr(elementsJsonFilePath, extensions);
549     }
550 }
551 
552 /**
553  * @brief process host firmware
554  *
555  * Allocate a callback context and register for DBus.ObjectManager Interfaces
556  * added signals from entity manager.
557  *
558  * Check the current entity manager object tree for a
559  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instance (entity
560  * manager will be dbus activated if it is not running).  If one is found,
561  * determine if symlinks need to be created and create them.  Instruct the
562  * program event loop to exit.
563  *
564  * If no instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
565  * found return the callback context to main, where the program will sleep
566  * until the callback is invoked one or more times and instructs the program
567  * event loop to exit when
568  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is added.
569  *
570  * @param[in] bus a DBus client connection
571  * @param[in] extensionMap a map of
572  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
573  * file extensions.
574  * @param[in] hostFirmwareDirectory The directory in which processHostFirmware
575  * should look for blob files.
576  * @param[in] errorCallback A callback made in the event of filesystem errors.
577  * @param[in] loop a program event loop
578  * @return nullptr if an instance of
579  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found, otherwise a
580  * pointer to an sdbusplus match object.
581  */
582 std::shared_ptr<void> processHostFirmware(
583     sdbusplus::bus::bus& bus,
584     std::map<std::string, std::vector<std::string>> extensionMap,
585     std::filesystem::path hostFirmwareDirectory,
586     ErrorCallbackType errorCallback, sdeventplus::Event& loop)
587 {
588     // ownership of extensionMap, hostFirmwareDirectory and errorCallback can't
589     // be transfered to the match callback because they are needed in the non
590     // async part of this function below, so they need to be moved to the heap.
591     auto pExtensionMap =
592         std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
593     auto pHostFirmwareDirectory =
594         std::make_shared<decltype(hostFirmwareDirectory)>(
595             std::move(hostFirmwareDirectory));
596     auto pErrorCallback =
597         std::make_shared<decltype(errorCallback)>(std::move(errorCallback));
598 
599     // register for a callback in case the IBMCompatibleSystem interface has
600     // not yet been published by entity manager.
601     auto interfacesAddedMatch = std::make_shared<sdbusplus::bus::match::match>(
602         bus,
603         sdbusplus::bus::match::rules::interfacesAdded() +
604             sdbusplus::bus::match::rules::sender(
605                 "xyz.openbmc_project.EntityManager"),
606         [pExtensionMap, pHostFirmwareDirectory, pErrorCallback,
607          &loop](auto& message) {
608             // bind the extension map, host firmware directory, and error
609             // callback to the maybeMakeLinks function.
610             auto maybeMakeLinksWithArgsBound =
611                 std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
612                           std::cref(*pHostFirmwareDirectory),
613                           std::placeholders::_1, std::cref(*pErrorCallback));
614 
615             // if the InterfacesAdded message contains an an instance of
616             // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to
617             // see if links are necessary on this system and if so, create
618             // them.
619             if (maybeCallMessage(message, maybeMakeLinksWithArgsBound))
620             {
621                 // The IBMCompatibleSystem interface was found and the links
622                 // were created if applicable.  Instruct the event loop /
623                 // subcommand to exit.
624                 loop.exit(0);
625             }
626         });
627 
628     // now that we'll get a callback in the event of an InterfacesAdded signal
629     // (potentially containing
630     // xyz.openbmc_project.Configuration.IBMCompatibleSystem), activate entity
631     // manager if it isn't running and enumerate its objects
632     auto getManagedObjects = bus.new_method_call(
633         "xyz.openbmc_project.EntityManager", "/",
634         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
635     std::map<std::string,
636              std::map<std::string, std::variant<std::vector<std::string>>>>
637         interfacesAndProperties;
638     std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)>
639         objects;
640     try
641     {
642         auto reply = bus.call(getManagedObjects);
643         reply.read(objects);
644     }
645     catch (const sdbusplus::exception::SdBusError& e)
646     {
647         // Error querying the EntityManager interface. Return the match to have
648         // the callback run if/when the interface appears in D-Bus.
649         return interfacesAddedMatch;
650     }
651 
652     // bind the extension map, host firmware directory, and error callback to
653     // the maybeMakeLinks function.
654     auto maybeMakeLinksWithArgsBound =
655         std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
656                   std::cref(*pHostFirmwareDirectory), std::placeholders::_1,
657                   std::cref(*pErrorCallback));
658 
659     for (const auto& pair : objects)
660     {
661         std::tie(std::ignore, interfacesAndProperties) = pair;
662         // if interfacesAndProperties contains an an instance of
663         // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to see
664         // if links are necessary on this system and if so, create them
665         if (maybeCall(interfacesAndProperties, maybeMakeLinksWithArgsBound))
666         {
667             // The IBMCompatibleSystem interface is already on the bus and the
668             // links were created if applicable.  Instruct the event loop to
669             // exit.
670             loop.exit(0);
671             // The match object isn't needed anymore, so destroy it on return.
672             return nullptr;
673         }
674     }
675 
676     // The IBMCompatibleSystem interface has not yet been published.  Move
677     // ownership of the match callback to the caller.
678     return interfacesAddedMatch;
679 }
680 
681 /**
682  * @brief Update the Bios Attribute Table
683  *
684  * If an instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
685  * found, update the Bios Attribute Table with the appropriate host firmware
686  * data.
687  *
688  * @param[in] bus - D-Bus client connection.
689  * @param[in] extensionMap - Map of IBMCompatibleSystem names and host firmware
690  *                           file extensions.
691  * @param[in] elementsJsonFilePath - The Path to the json file
692  * @param[in] loop - Program event loop.
693  * @return nullptr
694  */
695 std::vector<std::shared_ptr<void>> updateBiosAttrTable(
696     sdbusplus::bus::bus& bus,
697     std::map<std::string, std::vector<std::string>> extensionMap,
698     std::filesystem::path elementsJsonFilePath, sdeventplus::Event& loop)
699 {
700     constexpr auto pldmPath = "/xyz/openbmc_project/pldm";
701     constexpr auto entityManagerServiceName =
702         "xyz.openbmc_project.EntityManager";
703 
704     auto pExtensionMap =
705         std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
706     auto pElementsJsonFilePath =
707         std::make_shared<decltype(elementsJsonFilePath)>(
708             std::move(elementsJsonFilePath));
709 
710     // Entity Manager is needed to get the list of supported extensions. Add a
711     // match to monitor interfaces added in case it's not running yet.
712     auto maybeSetAttrWithArgsBound =
713         std::bind(maybeSetBiosAttr, std::cref(*pExtensionMap),
714                   std::cref(*pElementsJsonFilePath), std::placeholders::_1);
715 
716     std::vector<std::shared_ptr<void>> matches;
717     matches.emplace_back(std::make_shared<sdbusplus::bus::match::match>(
718         bus,
719         sdbusplus::bus::match::rules::interfacesAdded() +
720             sdbusplus::bus::match::rules::sender(
721                 "xyz.openbmc_project.EntityManager"),
722         [pldmPath, pExtensionMap, pElementsJsonFilePath,
723          maybeSetAttrWithArgsBound, &loop](auto& message) {
724             auto bus = sdbusplus::bus::new_default();
725             auto pldmObject = getObject(bus, pldmPath);
726             if (pldmObject.empty())
727             {
728                 return;
729             }
730             if (maybeCallMessage(message, maybeSetAttrWithArgsBound))
731             {
732                 loop.exit(0);
733             }
734         }));
735     matches.emplace_back(std::make_shared<sdbusplus::bus::match::match>(
736         bus,
737         sdbusplus::bus::match::rules::nameOwnerChanged() +
738             sdbusplus::bus::match::rules::arg0namespace(
739                 "xyz.openbmc_project.PLDM"),
740         [pExtensionMap, pElementsJsonFilePath, maybeSetAttrWithArgsBound,
741          &loop](auto& message) {
742             std::string name;
743             std::string oldOwner;
744             std::string newOwner;
745             message.read(name, oldOwner, newOwner);
746 
747             if (newOwner.empty())
748             {
749                 return;
750             }
751 
752             auto bus = sdbusplus::bus::new_default();
753             InterfacesPropertiesMap interfacesAndProperties;
754             auto objects = getManagedObjects(bus, entityManagerServiceName);
755             for (const auto& pair : objects)
756             {
757                 std::tie(std::ignore, interfacesAndProperties) = pair;
758                 if (maybeCall(interfacesAndProperties,
759                               maybeSetAttrWithArgsBound))
760                 {
761                     loop.exit(0);
762                 }
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. Return if it's not running.
768     auto pldmObject = getObject(bus, pldmPath);
769     if (pldmObject.empty())
770     {
771         return matches;
772     }
773 
774     InterfacesPropertiesMap interfacesAndProperties;
775     auto objects = getManagedObjects(bus, entityManagerServiceName);
776     for (const auto& pair : objects)
777     {
778         std::tie(std::ignore, interfacesAndProperties) = pair;
779         if (maybeCall(interfacesAndProperties, maybeSetAttrWithArgsBound))
780         {
781             loop.exit(0);
782             return {};
783         }
784     }
785 
786     return matches;
787 }
788 
789 } // namespace process_hostfirmware
790 } // namespace functions
791