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