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 (const 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         // and create a second entry with key name element_RT to specify it as
310         // a runtime element.
311         // Ex: if the JSON contains an entry A.P10 with lid name X, it'll create
312         // and try A=X. If the JSON also contained an entry A.P10.iplTime with
313         // lid name Y, the A entry would be overwritten to be A=Y and a second
314         // entry A_RT=X would be created.
315         constexpr auto iplExtension = ".iplTime";
316         constexpr auto runtimeSuffix = "_RT";
317         std::filesystem::path path(name);
318         if (path.extension() == iplExtension)
319         {
320             // Some elements have an additional extension, ex: .P10.iplTime
321             // Strip off the ipl extension with stem(), then check if there is
322             // an additional extension with extension().
323             if (!path.stem().extension().empty())
324             {
325                 // Check if the extension matches the extensions for this system
326                 if (std::find(extensions.begin(), extensions.end(),
327                               path.stem().extension()) == extensions.end())
328                 {
329                     continue;
330                 }
331             }
332             // Get the element name without extensions by calling stem() twice
333             // since stem() returns the base name if no periods are found.
334             // Therefore both "element.P10" and "element.P10.iplTime" would
335             // become "element".
336             auto keyName = path.stem().stem();
337             auto attrIt = attr.find(keyName);
338             if (attrIt != attr.end())
339             {
340                 // Copy the existing entry to a runtime entry
341                 auto runtimeKeyName = keyName.string() + runtimeSuffix;
342                 attr.insert({runtimeKeyName, attrIt->second});
343             }
344             // Overwrite the exsiting element with the ipl entry
345             attr[keyName] = lid;
346             continue;
347         }
348 
349         // Process all other extensions. The extension should match the list of
350         // supported extensions for this system. Use .insert() to only add
351         // entries that do not exist, so to not overwrite the values that may
352         // had been added that had the ipl extension.
353         if (std::find(extensions.begin(), extensions.end(), path.extension()) !=
354             extensions.end())
355         {
356             auto keyName = path.stem();
357             auto attrIt = attr.find(keyName);
358             if (attrIt != attr.end())
359             {
360                 // The existing entry is an ipl entry, therefore create this
361                 // entry as a runtime one.
362                 auto runtimeKeyName = keyName.string() + runtimeSuffix;
363                 attr.insert({runtimeKeyName, lid});
364             }
365             else
366             {
367                 attr.insert({path.stem(), lid});
368             }
369         }
370     }
371     for (const auto& a : attr)
372     {
373         // Build the bios attribute string with format:
374         // "element1=lid1,element2=lid2,elementN=lidN,"
375         biosAttrStr += a.first + "=" + a.second + ",";
376 
377         // Create symlinks from the hostfw elements to their corresponding
378         // lid files if they don't exist
379         auto elementFilePath =
380             std::filesystem::path("/media/hostfw/running") / a.first;
381         if (!std::filesystem::exists(elementFilePath))
382         {
383             std::error_code ec;
384             auto lidName = a.second + ".lid";
385             std::filesystem::create_symlink(lidName, elementFilePath, ec);
386             if (ec)
387             {
388                 log<level::ERR>("Error creating symlink",
389                                 entry("TARGET=%s", lidName.c_str()),
390                                 entry("LINK=%s", elementFilePath.c_str()));
391             }
392         }
393     }
394 
395     // Delete the last comma of the bios attribute string
396     if (biosAttrStr.back() == ',')
397     {
398         return biosAttrStr.substr(0, biosAttrStr.length() - 1);
399     }
400 
401     return biosAttrStr;
402 }
403 
404 /**
405  * @brief Set the bios attribute table with details of the host firmware data
406  * for this system.
407  *
408  * @param[in] elementsJsonFilePath - The path to the host firmware json file.
409  * @param[in] extentions - The extensions of the firmware blob files.
410  */
411 void setBiosAttr(const std::filesystem::path& elementsJsonFilePath,
412                  const std::vector<std::string>& extensions)
413 {
414     auto biosAttrStr = getBiosAttrStr(elementsJsonFilePath, extensions);
415 
416     constexpr auto biosConfigPath = "/xyz/openbmc_project/bios_config/manager";
417     constexpr auto biosConfigIntf = "xyz.openbmc_project.BIOSConfig.Manager";
418     constexpr auto dbusAttrName = "hb_lid_ids";
419     constexpr auto dbusAttrType =
420         "xyz.openbmc_project.BIOSConfig.Manager.AttributeType.String";
421 
422     using PendingAttributesType = std::vector<std::pair<
423         std::string, std::tuple<std::string, std::variant<std::string>>>>;
424     PendingAttributesType pendingAttributes;
425     pendingAttributes.emplace_back(std::make_pair(
426         dbusAttrName, std::make_tuple(dbusAttrType, biosAttrStr)));
427 
428     auto bus = sdbusplus::bus::new_default();
429     auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
430                                       MAPPER_INTERFACE, "GetObject");
431     method.append(biosConfigPath, std::vector<std::string>({biosConfigIntf}));
432     std::vector<std::pair<std::string, std::vector<std::string>>> response;
433     try
434     {
435         auto reply = bus.call(method);
436         reply.read(response);
437         if (response.empty())
438         {
439             log<level::ERR>("Error reading mapper response",
440                             entry("PATH=%s", biosConfigPath),
441                             entry("INTERFACE=%s", biosConfigIntf));
442             return;
443         }
444         auto method = bus.new_method_call((response.begin()->first).c_str(),
445                                           biosConfigPath,
446                                           SYSTEMD_PROPERTY_INTERFACE, "Set");
447         method.append(biosConfigIntf, "PendingAttributes",
448                       std::variant<PendingAttributesType>(pendingAttributes));
449         bus.call(method);
450     }
451     catch (const sdbusplus::exception::exception& e)
452     {
453         log<level::ERR>("Error setting the bios attribute",
454                         entry("ERROR=%s", e.what()),
455                         entry("ATTRIBUTE=%s", dbusAttrName));
456         return;
457     }
458 }
459 
460 /**
461  * @brief Make callbacks on
462  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
463  *
464  * Look for an instance of
465  * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
466  * argument and if found, issue the provided callback.
467  *
468  * @param[in] interfacesAndProperties the interfaces in which to look for an
469  * instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem
470  * @param[in] callback the user callback to make if
471  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
472  * interfacesAndProperties
473  * @return true if interfacesAndProperties contained an instance of
474  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
475  */
476 bool maybeCall(const std::map<std::string,
477                               std::map<std::string,
478                                        std::variant<std::vector<std::string>>>>&
479                    interfacesAndProperties,
480                const MaybeCallCallbackType& callback)
481 {
482     using namespace std::string_literals;
483 
484     static const auto interfaceName =
485         "xyz.openbmc_project.Configuration.IBMCompatibleSystem"s;
486     auto interfaceIterator = interfacesAndProperties.find(interfaceName);
487     if (interfaceIterator == interfacesAndProperties.cend())
488     {
489         // IBMCompatibleSystem interface not found, so instruct the caller to
490         // keep waiting or try again later.
491         return false;
492     }
493     auto propertyIterator = interfaceIterator->second.find("Names"s);
494     if (propertyIterator == interfaceIterator->second.cend())
495     {
496         // The interface exists but the property doesn't.  This is a bug in the
497         // IBMCompatibleSystem implementation.  The caller should not try
498         // again.
499         std::cerr << "Names property not implemented on " << interfaceName
500                   << "\n";
501         return true;
502     }
503 
504     const auto& ibmCompatibleSystem =
505         std::get<std::vector<std::string>>(propertyIterator->second);
506     if (callback)
507     {
508         callback(ibmCompatibleSystem);
509     }
510 
511     // IBMCompatibleSystem found and callback issued.
512     return true;
513 }
514 
515 /**
516  * @brief Make callbacks on
517  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
518  *
519  * Look for an instance of
520  * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
521  * argument and if found, issue the provided callback.
522  *
523  * @param[in] message the DBus message in which to look for an instance of
524  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
525  * @param[in] callback the user callback to make if
526  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
527  * message
528  * @return true if message contained an instance of
529  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
530  */
531 bool maybeCallMessage(sdbusplus::message::message& message,
532                       const MaybeCallCallbackType& callback)
533 {
534     std::map<std::string,
535              std::map<std::string, std::variant<std::vector<std::string>>>>
536         interfacesAndProperties;
537     sdbusplus::message::object_path _;
538     message.read(_, interfacesAndProperties);
539     return maybeCall(interfacesAndProperties, callback);
540 }
541 
542 /**
543  * @brief Determine system support for host firmware well-known names.
544  *
545  * Using the provided extensionMap and
546  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if
547  * well-known names for host firmare blob files are necessary and if so, create
548  * them.
549  *
550  * @param[in] extensionMap a map of
551  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
552  * file extensions.
553  * @param[in] hostFirmwareDirectory The directory in which findLinks should
554  * look for host firmware blob files that need well-known names.
555  * @param[in] ibmCompatibleSystem The names property of an instance of
556  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
557  * @param[in] errorCallback A callback made in the event of filesystem errors.
558  */
559 void maybeMakeLinks(
560     const std::map<std::string, std::vector<std::string>>& extensionMap,
561     const std::filesystem::path& hostFirmwareDirectory,
562     const std::vector<std::string>& ibmCompatibleSystem,
563     const ErrorCallbackType& errorCallback)
564 {
565     std::vector<std::string> extensions;
566     if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
567                                             extensions))
568     {
569         findLinks(hostFirmwareDirectory, extensions, errorCallback, writeLink);
570     }
571 }
572 
573 /**
574  * @brief Determine system support for updating the bios attribute table.
575  *
576  * Using the provided extensionMap and
577  * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if the bios
578  * attribute table needs to be updated.
579  *
580  * @param[in] extensionMap a map of
581  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
582  * file extensions.
583  * @param[in] elementsJsonFilePath The file path to the json file
584  * @param[in] ibmCompatibleSystem The names property of an instance of
585  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
586  */
587 void maybeSetBiosAttr(
588     const std::map<std::string, std::vector<std::string>>& extensionMap,
589     const std::filesystem::path& elementsJsonFilePath,
590     const std::vector<std::string>& ibmCompatibleSystem)
591 {
592     std::vector<std::string> extensions;
593     if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
594                                             extensions))
595     {
596         setBiosAttr(elementsJsonFilePath, extensions);
597     }
598 }
599 
600 /**
601  * @brief process host firmware
602  *
603  * Allocate a callback context and register for DBus.ObjectManager Interfaces
604  * added signals from entity manager.
605  *
606  * Check the current entity manager object tree for a
607  * xyz.openbmc_project.Configuration.IBMCompatibleSystem instance (entity
608  * manager will be dbus activated if it is not running).  If one is found,
609  * determine if symlinks need to be created and create them.  Instruct the
610  * program event loop to exit.
611  *
612  * If no instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
613  * found return the callback context to main, where the program will sleep
614  * until the callback is invoked one or more times and instructs the program
615  * event loop to exit when
616  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is added.
617  *
618  * @param[in] bus a DBus client connection
619  * @param[in] extensionMap a map of
620  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
621  * file extensions.
622  * @param[in] hostFirmwareDirectory The directory in which processHostFirmware
623  * should look for blob files.
624  * @param[in] errorCallback A callback made in the event of filesystem errors.
625  * @param[in] loop a program event loop
626  * @return nullptr if an instance of
627  * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found, otherwise a
628  * pointer to an sdbusplus match object.
629  */
630 std::shared_ptr<void> processHostFirmware(
631     sdbusplus::bus::bus& bus,
632     std::map<std::string, std::vector<std::string>> extensionMap,
633     std::filesystem::path hostFirmwareDirectory,
634     ErrorCallbackType errorCallback, sdeventplus::Event& loop)
635 {
636     // ownership of extensionMap, hostFirmwareDirectory and errorCallback can't
637     // be transfered to the match callback because they are needed in the non
638     // async part of this function below, so they need to be moved to the heap.
639     auto pExtensionMap =
640         std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
641     auto pHostFirmwareDirectory =
642         std::make_shared<decltype(hostFirmwareDirectory)>(
643             std::move(hostFirmwareDirectory));
644     auto pErrorCallback =
645         std::make_shared<decltype(errorCallback)>(std::move(errorCallback));
646 
647     // register for a callback in case the IBMCompatibleSystem interface has
648     // not yet been published by entity manager.
649     auto interfacesAddedMatch = std::make_shared<sdbusplus::bus::match::match>(
650         bus,
651         sdbusplus::bus::match::rules::interfacesAdded() +
652             sdbusplus::bus::match::rules::sender(
653                 "xyz.openbmc_project.EntityManager"),
654         [pExtensionMap, pHostFirmwareDirectory, pErrorCallback,
655          &loop](auto& message) {
656             // bind the extension map, host firmware directory, and error
657             // callback to the maybeMakeLinks function.
658             auto maybeMakeLinksWithArgsBound =
659                 std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
660                           std::cref(*pHostFirmwareDirectory),
661                           std::placeholders::_1, std::cref(*pErrorCallback));
662 
663             // if the InterfacesAdded message contains an an instance of
664             // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to
665             // see if links are necessary on this system and if so, create
666             // them.
667             if (maybeCallMessage(message, maybeMakeLinksWithArgsBound))
668             {
669                 // The IBMCompatibleSystem interface was found and the links
670                 // were created if applicable.  Instruct the event loop /
671                 // subcommand to exit.
672                 loop.exit(0);
673             }
674         });
675 
676     // now that we'll get a callback in the event of an InterfacesAdded signal
677     // (potentially containing
678     // xyz.openbmc_project.Configuration.IBMCompatibleSystem), activate entity
679     // manager if it isn't running and enumerate its objects
680     auto getManagedObjects = bus.new_method_call(
681         "xyz.openbmc_project.EntityManager", "/",
682         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
683     std::map<std::string,
684              std::map<std::string, std::variant<std::vector<std::string>>>>
685         interfacesAndProperties;
686     std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)>
687         objects;
688     try
689     {
690         auto reply = bus.call(getManagedObjects);
691         reply.read(objects);
692     }
693     catch (const sdbusplus::exception::exception& e)
694     {
695         // Error querying the EntityManager interface. Return the match to have
696         // the callback run if/when the interface appears in D-Bus.
697         return interfacesAddedMatch;
698     }
699 
700     // bind the extension map, host firmware directory, and error callback to
701     // the maybeMakeLinks function.
702     auto maybeMakeLinksWithArgsBound =
703         std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
704                   std::cref(*pHostFirmwareDirectory), std::placeholders::_1,
705                   std::cref(*pErrorCallback));
706 
707     for (const auto& pair : objects)
708     {
709         std::tie(std::ignore, interfacesAndProperties) = pair;
710         // if interfacesAndProperties contains an an instance of
711         // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to see
712         // if links are necessary on this system and if so, create them
713         if (maybeCall(interfacesAndProperties, maybeMakeLinksWithArgsBound))
714         {
715             // The IBMCompatibleSystem interface is already on the bus and the
716             // links were created if applicable.  Instruct the event loop to
717             // exit.
718             loop.exit(0);
719             // The match object isn't needed anymore, so destroy it on return.
720             return nullptr;
721         }
722     }
723 
724     // The IBMCompatibleSystem interface has not yet been published.  Move
725     // ownership of the match callback to the caller.
726     return interfacesAddedMatch;
727 }
728 
729 /**
730  * @brief Update the Bios Attribute Table
731  *
732  * If an instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
733  * found, update the Bios Attribute Table with the appropriate host firmware
734  * data.
735  *
736  * @param[in] bus - D-Bus client connection.
737  * @param[in] extensionMap - Map of IBMCompatibleSystem names and host firmware
738  *                           file extensions.
739  * @param[in] elementsJsonFilePath - The Path to the json file
740  * @param[in] loop - Program event loop.
741  * @return nullptr
742  */
743 std::vector<std::shared_ptr<void>> updateBiosAttrTable(
744     sdbusplus::bus::bus& bus,
745     std::map<std::string, std::vector<std::string>> extensionMap,
746     std::filesystem::path elementsJsonFilePath, sdeventplus::Event& loop)
747 {
748     constexpr auto pldmPath = "/xyz/openbmc_project/pldm";
749     constexpr auto entityManagerServiceName =
750         "xyz.openbmc_project.EntityManager";
751 
752     auto pExtensionMap =
753         std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
754     auto pElementsJsonFilePath =
755         std::make_shared<decltype(elementsJsonFilePath)>(
756             std::move(elementsJsonFilePath));
757 
758     // Entity Manager is needed to get the list of supported extensions. Add a
759     // match to monitor interfaces added in case it's not running yet.
760     auto maybeSetAttrWithArgsBound =
761         std::bind(maybeSetBiosAttr, std::cref(*pExtensionMap),
762                   std::cref(*pElementsJsonFilePath), std::placeholders::_1);
763 
764     std::vector<std::shared_ptr<void>> matches;
765     matches.emplace_back(std::make_shared<sdbusplus::bus::match::match>(
766         bus,
767         sdbusplus::bus::match::rules::interfacesAdded() +
768             sdbusplus::bus::match::rules::sender(
769                 "xyz.openbmc_project.EntityManager"),
770         [pldmPath, pExtensionMap, pElementsJsonFilePath,
771          maybeSetAttrWithArgsBound, &loop](auto& message) {
772             auto bus = sdbusplus::bus::new_default();
773             auto pldmObject = getObject(bus, pldmPath);
774             if (pldmObject.empty())
775             {
776                 return;
777             }
778             if (maybeCallMessage(message, maybeSetAttrWithArgsBound))
779             {
780                 loop.exit(0);
781             }
782         }));
783     matches.emplace_back(std::make_shared<sdbusplus::bus::match::match>(
784         bus,
785         sdbusplus::bus::match::rules::nameOwnerChanged() +
786             sdbusplus::bus::match::rules::arg0namespace(
787                 "xyz.openbmc_project.PLDM"),
788         [pExtensionMap, pElementsJsonFilePath, maybeSetAttrWithArgsBound,
789          &loop](auto& message) {
790             std::string name;
791             std::string oldOwner;
792             std::string newOwner;
793             message.read(name, oldOwner, newOwner);
794 
795             if (newOwner.empty())
796             {
797                 return;
798             }
799 
800             auto bus = sdbusplus::bus::new_default();
801             InterfacesPropertiesMap interfacesAndProperties;
802             auto objects = getManagedObjects(bus, entityManagerServiceName);
803             for (const auto& pair : objects)
804             {
805                 std::tie(std::ignore, interfacesAndProperties) = pair;
806                 if (maybeCall(interfacesAndProperties,
807                               maybeSetAttrWithArgsBound))
808                 {
809                     loop.exit(0);
810                 }
811             }
812         }));
813 
814     // The BIOS attribute table can only be updated if PLDM is running because
815     // PLDM is the one that exposes this property. Return if it's not running.
816     auto pldmObject = getObject(bus, pldmPath);
817     if (pldmObject.empty())
818     {
819         return matches;
820     }
821 
822     InterfacesPropertiesMap interfacesAndProperties;
823     auto objects = getManagedObjects(bus, entityManagerServiceName);
824     for (const auto& pair : objects)
825     {
826         std::tie(std::ignore, interfacesAndProperties) = pair;
827         if (maybeCall(interfacesAndProperties, maybeSetAttrWithArgsBound))
828         {
829             loop.exit(0);
830             return {};
831         }
832     }
833 
834     return matches;
835 }
836 
837 } // namespace process_hostfirmware
838 } // namespace functions
839