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