1 #include "config.h"
2 
3 #include "item_updater.hpp"
4 
5 #include "runtime_warning.hpp"
6 #include "utils.hpp"
7 
8 #include <phosphor-logging/elog-errors.hpp>
9 #include <phosphor-logging/lg2.hpp>
10 #include <xyz/openbmc_project/Common/error.hpp>
11 
12 #include <exception>
13 #include <filesystem>
14 #include <format>
15 #include <set>
16 #include <stdexcept>
17 
18 namespace
19 {
20 constexpr auto MANIFEST_VERSION = "version";
21 constexpr auto MANIFEST_EXTENDED_VERSION = "extended_version";
22 } // namespace
23 
24 namespace phosphor
25 {
26 namespace software
27 {
28 namespace updater
29 {
30 namespace server = sdbusplus::xyz::openbmc_project::Software::server;
31 
32 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
33 using namespace phosphor::logging;
34 using SVersion = server::Version;
35 using VersionPurpose = SVersion::VersionPurpose;
36 
onVersionInterfacesAddedMsg(sdbusplus::message_t & msg)37 void ItemUpdater::onVersionInterfacesAddedMsg(sdbusplus::message_t& msg)
38 {
39     try
40     {
41         sdbusplus::message::object_path objPath;
42         InterfacesAddedMap interfaces;
43         msg.read(objPath, interfaces);
44 
45         std::string path(std::move(objPath));
46         onVersionInterfacesAdded(path, interfaces);
47     }
48     catch (const std::exception& e)
49     {
50         lg2::error("Unable to handle version InterfacesAdded event: {ERROR}",
51                    "ERROR", e);
52     }
53 }
54 
onVersionInterfacesAdded(const std::string & path,const InterfacesAddedMap & interfaces)55 void ItemUpdater::onVersionInterfacesAdded(const std::string& path,
56                                            const InterfacesAddedMap& interfaces)
57 {
58     std::string filePath;
59     auto purpose = VersionPurpose::Unknown;
60     std::string version;
61 
62     for (const auto& [interfaceName, propertyMap] : interfaces)
63     {
64         if (interfaceName == VERSION_IFACE)
65         {
66             for (const auto& [propertyName, propertyValue] : propertyMap)
67             {
68                 if (propertyName == "Purpose")
69                 {
70                     // Only process the PSU images
71                     auto value = SVersion::convertVersionPurposeFromString(
72                         std::get<std::string>(propertyValue));
73 
74                     if (value == VersionPurpose::PSU)
75                     {
76                         purpose = value;
77                     }
78                 }
79                 else if (propertyName == VERSION)
80                 {
81                     version = std::get<std::string>(propertyValue);
82                 }
83             }
84         }
85         else if (interfaceName == FILEPATH_IFACE)
86         {
87             const auto& it = propertyMap.find("Path");
88             if (it != propertyMap.end())
89             {
90                 filePath = std::get<std::string>(it->second);
91             }
92         }
93     }
94     if ((filePath.empty()) || (purpose == VersionPurpose::Unknown))
95     {
96         return;
97     }
98 
99     // If we are only installing PSU images from the built-in directory, ignore
100     // PSU images from other directories
101     if (ALWAYS_USE_BUILTIN_IMG_DIR && !filePath.starts_with(IMG_DIR_BUILTIN))
102     {
103         return;
104     }
105 
106     // Version id is the last item in the path
107     auto pos = path.rfind('/');
108     if (pos == std::string::npos)
109     {
110         lg2::error("No version id found in object path {OBJPATH}", "OBJPATH",
111                    path);
112         return;
113     }
114 
115     auto versionId = path.substr(pos + 1);
116 
117     if (activations.find(versionId) == activations.end())
118     {
119         // Determine the Activation state by processing the given image dir.
120         AssociationList associations;
121         auto activationState = Activation::Status::Ready;
122 
123         associations.emplace_back(std::make_tuple(
124             ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION,
125             PSU_INVENTORY_PATH_BASE));
126 
127         fs::path manifestPath(filePath);
128         manifestPath /= MANIFEST_FILE;
129         std::string extendedVersion =
130             Version::getValue(manifestPath, {MANIFEST_EXTENDED_VERSION});
131 
132         auto activation =
133             createActivationObject(path, versionId, extendedVersion,
134                                    activationState, associations, filePath);
135         activations.emplace(versionId, std::move(activation));
136 
137         auto versionPtr =
138             createVersionObject(path, versionId, version, purpose);
139         versions.emplace(versionId, std::move(versionPtr));
140     }
141 }
142 
erase(const std::string & versionId)143 void ItemUpdater::erase(const std::string& versionId)
144 {
145     auto it = versions.find(versionId);
146     if (it == versions.end())
147     {
148         lg2::error("Error: Failed to find version {VERSION_ID} in "
149                    "item updater versions map. Unable to remove.",
150                    "VERSION_ID", versionId);
151     }
152     else
153     {
154         versionStrings.erase(it->second->getVersionString());
155         versions.erase(it);
156     }
157 
158     // Removing entry in activations map
159     auto ita = activations.find(versionId);
160     if (ita == activations.end())
161     {
162         lg2::error("Error: Failed to find version {VERSION_ID} in "
163                    "item updater activations map. Unable to remove.",
164                    "VERSION_ID", versionId);
165     }
166     else
167     {
168         activations.erase(versionId);
169     }
170 }
171 
createActiveAssociation(const std::string & path)172 void ItemUpdater::createActiveAssociation(const std::string& path)
173 {
174     assocs.emplace_back(
175         std::make_tuple(ACTIVE_FWD_ASSOCIATION, ACTIVE_REV_ASSOCIATION, path));
176     associations(assocs);
177 }
178 
addFunctionalAssociation(const std::string & path)179 void ItemUpdater::addFunctionalAssociation(const std::string& path)
180 {
181     assocs.emplace_back(std::make_tuple(FUNCTIONAL_FWD_ASSOCIATION,
182                                         FUNCTIONAL_REV_ASSOCIATION, path));
183     associations(assocs);
184 }
185 
addUpdateableAssociation(const std::string & path)186 void ItemUpdater::addUpdateableAssociation(const std::string& path)
187 {
188     assocs.emplace_back(std::make_tuple(UPDATEABLE_FWD_ASSOCIATION,
189                                         UPDATEABLE_REV_ASSOCIATION, path));
190     associations(assocs);
191 }
192 
removeAssociation(const std::string & path)193 void ItemUpdater::removeAssociation(const std::string& path)
194 {
195     for (auto iter = assocs.begin(); iter != assocs.end();)
196     {
197         if ((std::get<2>(*iter)) == path)
198         {
199             iter = assocs.erase(iter);
200             associations(assocs);
201         }
202         else
203         {
204             ++iter;
205         }
206     }
207 }
208 
onUpdateDone(const std::string & versionId,const std::string & psuInventoryPath)209 void ItemUpdater::onUpdateDone(const std::string& versionId,
210                                const std::string& psuInventoryPath)
211 {
212     // After update is done, remove old activation objects
213     for (auto it = activations.begin(); it != activations.end(); ++it)
214     {
215         if (it->second->getVersionId() != versionId &&
216             utils::isAssociated(psuInventoryPath, it->second->associations()))
217         {
218             removePsuObject(psuInventoryPath);
219             break;
220         }
221     }
222 
223     auto it = activations.find(versionId);
224     if (it == activations.end())
225     {
226         lg2::error("Unable to find Activation for version ID {VERSION_ID}",
227                    "VERSION_ID", versionId);
228     }
229     else
230     {
231         psuPathActivationMap.emplace(psuInventoryPath, it->second);
232     }
233 }
234 
createActivationObject(const std::string & path,const std::string & versionId,const std::string & extVersion,Activation::Status activationStatus,const AssociationList & assocs,const std::string & filePath)235 std::unique_ptr<Activation> ItemUpdater::createActivationObject(
236     const std::string& path, const std::string& versionId,
237     const std::string& extVersion, Activation::Status activationStatus,
238     const AssociationList& assocs, const std::string& filePath)
239 {
240     return std::make_unique<Activation>(bus, path, versionId, extVersion,
241                                         activationStatus, assocs, filePath,
242                                         this, this);
243 }
244 
createPsuObject(const std::string & psuInventoryPath,const std::string & psuVersion)245 void ItemUpdater::createPsuObject(const std::string& psuInventoryPath,
246                                   const std::string& psuVersion)
247 {
248     auto versionId = utils::getVersionId(psuVersion);
249     auto path = std::string(SOFTWARE_OBJPATH) + "/" + versionId;
250 
251     auto it = activations.find(versionId);
252     if (it != activations.end())
253     {
254         // The versionId is already created, associate the path
255         auto associations = it->second->associations();
256         associations.emplace_back(
257             std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
258                             ACTIVATION_REV_ASSOCIATION, psuInventoryPath));
259         it->second->associations(associations);
260         psuPathActivationMap.emplace(psuInventoryPath, it->second);
261     }
262     else
263     {
264         // Create a new object for running PSU inventory
265         AssociationList associations;
266         auto activationState = Activation::Status::Active;
267 
268         associations.emplace_back(
269             std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
270                             ACTIVATION_REV_ASSOCIATION, psuInventoryPath));
271 
272         auto activation = createActivationObject(
273             path, versionId, "", activationState, associations, "");
274         activations.emplace(versionId, std::move(activation));
275         psuPathActivationMap.emplace(psuInventoryPath, activations[versionId]);
276 
277         auto versionPtr = createVersionObject(path, versionId, psuVersion,
278                                               VersionPurpose::PSU);
279         versions.emplace(versionId, std::move(versionPtr));
280 
281         createActiveAssociation(path);
282         addFunctionalAssociation(path);
283         addUpdateableAssociation(path);
284     }
285 }
286 
removePsuObject(const std::string & psuInventoryPath)287 void ItemUpdater::removePsuObject(const std::string& psuInventoryPath)
288 {
289     auto it = psuPathActivationMap.find(psuInventoryPath);
290     if (it == psuPathActivationMap.end())
291     {
292         lg2::error("No Activation found for PSU {PSUPATH}", "PSUPATH",
293                    psuInventoryPath);
294         return;
295     }
296     const auto& activationPtr = it->second;
297     psuPathActivationMap.erase(psuInventoryPath);
298 
299     auto associations = activationPtr->associations();
300     for (auto iter = associations.begin(); iter != associations.end();)
301     {
302         if ((std::get<2>(*iter)) == psuInventoryPath)
303         {
304             iter = associations.erase(iter);
305         }
306         else
307         {
308             ++iter;
309         }
310     }
311     if (associations.empty())
312     {
313         // Remove the activation
314         erase(activationPtr->getVersionId());
315     }
316     else
317     {
318         // Update association
319         activationPtr->associations(associations);
320     }
321 }
322 
addPsuToStatusMap(const std::string & psuPath)323 void ItemUpdater::addPsuToStatusMap(const std::string& psuPath)
324 {
325     if (!psuStatusMap.contains(psuPath))
326     {
327         psuStatusMap[psuPath] = {false, ""};
328 
329         // Add PropertiesChanged listener for Item interface so we are notified
330         // when Present property changes
331         psuMatches.emplace_back(
332             bus, MatchRules::propertiesChanged(psuPath, ITEM_IFACE),
333             std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this,
334                       std::placeholders::_1));
335     }
336 }
337 
handlePSUPresenceChanged(const std::string & psuPath)338 void ItemUpdater::handlePSUPresenceChanged(const std::string& psuPath)
339 {
340     if (psuStatusMap.contains(psuPath))
341     {
342         if (psuStatusMap[psuPath].present)
343         {
344             // PSU is now present
345             psuStatusMap[psuPath].model = utils::getModel(psuPath);
346             auto version = utils::getVersion(psuPath);
347             if (!version.empty() && !psuPathActivationMap.contains(psuPath))
348             {
349                 createPsuObject(psuPath, version);
350             }
351         }
352         else
353         {
354             // PSU is now missing
355             psuStatusMap[psuPath].model.clear();
356             if (psuPathActivationMap.contains(psuPath))
357             {
358                 removePsuObject(psuPath);
359             }
360         }
361     }
362 }
363 
createVersionObject(const std::string & objPath,const std::string & versionId,const std::string & versionString,sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose versionPurpose)364 std::unique_ptr<Version> ItemUpdater::createVersionObject(
365     const std::string& objPath, const std::string& versionId,
366     const std::string& versionString,
367     sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose
368         versionPurpose)
369 {
370     versionStrings.insert(versionString);
371     auto version = std::make_unique<Version>(
372         bus, objPath, versionId, versionString, versionPurpose,
373         std::bind(&ItemUpdater::erase, this, std::placeholders::_1));
374     return version;
375 }
376 
onPsuInventoryChangedMsg(sdbusplus::message_t & msg)377 void ItemUpdater::onPsuInventoryChangedMsg(sdbusplus::message_t& msg)
378 {
379     try
380     {
381         using Interface = std::string;
382         Interface interface;
383         Properties properties;
384         std::string psuPath = msg.get_path();
385 
386         msg.read(interface, properties);
387         onPsuInventoryChanged(psuPath, properties);
388     }
389     catch (const std::exception& e)
390     {
391         lg2::error(
392             "Unable to handle inventory PropertiesChanged event: {ERROR}",
393             "ERROR", e);
394     }
395 }
396 
onPsuInventoryChanged(const std::string & psuPath,const Properties & properties)397 void ItemUpdater::onPsuInventoryChanged(const std::string& psuPath,
398                                         const Properties& properties)
399 {
400     if (psuStatusMap.contains(psuPath) && properties.contains(PRESENT))
401     {
402         psuStatusMap[psuPath].present = std::get<bool>(properties.at(PRESENT));
403         handlePSUPresenceChanged(psuPath);
404         if (psuStatusMap[psuPath].present)
405         {
406             // Check if there are new PSU images to update
407             processStoredImage();
408             syncToLatestImage();
409         }
410     }
411 }
412 
processPSUImage()413 void ItemUpdater::processPSUImage()
414 {
415     try
416     {
417         auto paths = utils::getPSUInventoryPaths(bus);
418         for (const auto& p : paths)
419         {
420             try
421             {
422                 addPsuToStatusMap(p);
423                 auto service = utils::getService(bus, p.c_str(), ITEM_IFACE);
424                 psuStatusMap[p].present = utils::getProperty<bool>(
425                     bus, service.c_str(), p.c_str(), ITEM_IFACE, PRESENT);
426                 handlePSUPresenceChanged(p);
427             }
428             catch (const std::exception& e)
429             {
430                 // Ignore errors; the information might not be available yet
431             }
432         }
433     }
434     catch (const std::exception& e)
435     {
436         // Ignore errors; the information might not be available yet
437     }
438 }
439 
processStoredImage()440 void ItemUpdater::processStoredImage()
441 {
442     // Build list of directories to scan
443     std::vector<fs::path> paths;
444     paths.emplace_back(IMG_DIR_BUILTIN);
445     if (!ALWAYS_USE_BUILTIN_IMG_DIR)
446     {
447         paths.emplace_back(IMG_DIR_PERSIST);
448     }
449 
450     // Scan directories
451     auto logMsg = "Unable to find PSU firmware in directory {PATH}: {ERROR}";
452     for (const auto& path : paths)
453     {
454         try
455         {
456             scanDirectory(path);
457         }
458         catch (const RuntimeWarning& r)
459         {
460             lg2::warning(logMsg, "PATH", path, "ERROR", r);
461         }
462         catch (const std::exception& e)
463         {
464             lg2::error(logMsg, "PATH", path, "ERROR", e);
465         }
466     }
467 }
468 
scanDirectory(const fs::path & dir)469 void ItemUpdater::scanDirectory(const fs::path& dir)
470 {
471     // Find the model subdirectory within the specified directory
472     auto modelDir = findModelDirectory(dir);
473     if (modelDir.empty())
474     {
475         return;
476     }
477 
478     // Verify a manifest file exists within the model subdirectory
479     auto manifest = modelDir / MANIFEST_FILE;
480     if (!fs::exists(manifest))
481     {
482         throw std::runtime_error{
483             std::format("Manifest file does not exist: {}", manifest.c_str())};
484     }
485     if (!fs::is_regular_file(manifest))
486     {
487         throw std::runtime_error{
488             std::format("Path is not a file: {}", manifest.c_str())};
489     }
490 
491     // Get version, extVersion, and model from manifest file
492     auto ret = Version::getValues(
493         manifest.string(), {MANIFEST_VERSION, MANIFEST_EXTENDED_VERSION});
494     auto version = ret[MANIFEST_VERSION];
495     auto extVersion = ret[MANIFEST_EXTENDED_VERSION];
496     auto info = Version::getExtVersionInfo(extVersion);
497     auto model = info["model"];
498 
499     // Verify version and model are valid
500     if (version.empty() || model.empty())
501     {
502         throw std::runtime_error{std::format(
503             "Invalid information in manifest: path={}, version={}, model={}",
504             manifest.c_str(), version, model)};
505     }
506 
507     // Verify model from manifest matches the subdirectory name
508     if (modelDir.stem() != model)
509     {
510         throw std::runtime_error{std::format(
511             "Model in manifest does not match path: model={}, path={}", model,
512             modelDir.c_str())};
513     }
514 
515     // Found a valid PSU image directory; write path to journal
516     lg2::info("Found PSU firmware image directory: {PATH}", "PATH", modelDir);
517 
518     // Calculate version ID and check if an Activation for it exists
519     auto versionId = utils::getVersionId(version);
520     auto it = activations.find(versionId);
521     if (it == activations.end())
522     {
523         // This is a version that is different than the running PSUs
524         auto activationState = Activation::Status::Ready;
525         auto purpose = VersionPurpose::PSU;
526         auto objPath = std::string(SOFTWARE_OBJPATH) + "/" + versionId;
527 
528         auto activation = createActivationObject(objPath, versionId, extVersion,
529                                                  activationState, {}, modelDir);
530         activations.emplace(versionId, std::move(activation));
531 
532         auto versionPtr =
533             createVersionObject(objPath, versionId, version, purpose);
534         versions.emplace(versionId, std::move(versionPtr));
535     }
536     else
537     {
538         // Activation already exists. It may have been created for code that is
539         // running on one or more PSUs. Set Path and ExtendedVersion properties.
540         // The properties are not set when the Activation is created for code
541         // running on a PSU. The properties are needed to update other PSUs.
542         it->second->path(modelDir);
543         it->second->extendedVersion(extVersion);
544     }
545 }
546 
findModelDirectory(const fs::path & dir)547 fs::path ItemUpdater::findModelDirectory(const fs::path& dir)
548 {
549     fs::path modelDir;
550 
551     // Verify directory path exists and is a directory
552     if (!fs::exists(dir))
553     {
554         // Warning condition. IMG_DIR_BUILTIN might not be used. IMG_DIR_PERSIST
555         // might not exist if an image from IMG_DIR has not been stored.
556         throw RuntimeWarning{
557             std::format("Directory does not exist: {}", dir.c_str())};
558     }
559     if (!fs::is_directory(dir))
560     {
561         throw std::runtime_error{
562             std::format("Path is not a directory: {}", dir.c_str())};
563     }
564 
565     // Get the model name of the PSUs that have been found.  Note that we
566     // might not have found the PSU information yet on D-Bus.
567     std::string model;
568     for (const auto& [key, item] : psuStatusMap)
569     {
570         if (!item.model.empty())
571         {
572             model = item.model;
573             break;
574         }
575     }
576     if (!model.empty())
577     {
578         // Verify model subdirectory path exists and is a directory
579         auto subDir = dir / model;
580         if (!fs::exists(subDir))
581         {
582             // Warning condition. Subdirectory may not exist in IMG_DIR_PERSIST
583             // if no image has been stored there.  May also not exist if
584             // firmware update is not supported for this PSU model.
585             throw RuntimeWarning{
586                 std::format("Directory does not exist: {}", subDir.c_str())};
587         }
588         if (!fs::is_directory(subDir))
589         {
590             throw std::runtime_error{
591                 std::format("Path is not a directory: {}", subDir.c_str())};
592         }
593         modelDir = subDir;
594     }
595 
596     return modelDir;
597 }
598 
getLatestVersionId()599 std::optional<std::string> ItemUpdater::getLatestVersionId()
600 {
601     std::string latestVersion;
602     if (ALWAYS_USE_BUILTIN_IMG_DIR)
603     {
604         latestVersion = getFWVersionFromBuiltinDir();
605     }
606     else
607     {
608         latestVersion = utils::getLatestVersion(versionStrings);
609     }
610     if (latestVersion.empty())
611     {
612         return {};
613     }
614 
615     std::optional<std::string> versionId;
616     for (const auto& v : versions)
617     {
618         if (v.second->version() == latestVersion)
619         {
620             versionId = v.first;
621             break;
622         }
623     }
624     if (!versionId.has_value())
625     {
626         lg2::error("Unable to find versionId for latest version {VERSION}",
627                    "VERSION", latestVersion);
628     }
629     return versionId;
630 }
631 
syncToLatestImage()632 void ItemUpdater::syncToLatestImage()
633 {
634     auto latestVersionId = getLatestVersionId();
635     if (!latestVersionId)
636     {
637         return;
638     }
639     const auto& it = activations.find(*latestVersionId);
640     if (it == activations.end())
641 
642     {
643         lg2::error("Unable to find Activation for versionId {VERSION_ID}",
644                    "VERSION_ID", *latestVersionId);
645         return;
646     }
647     const auto& activation = it->second;
648     const auto& assocs = activation->associations();
649 
650     auto paths = utils::getPSUInventoryPaths(bus);
651     for (const auto& p : paths)
652     {
653         // If there is a present PSU that is not associated with the latest
654         // image, run the activation so that all PSUs are running the same
655         // latest image.
656         if (psuStatusMap.contains(p) && psuStatusMap[p].present)
657         {
658             if (!utils::isAssociated(p, assocs))
659             {
660                 lg2::info("Automatically update PSUs to versionId {VERSION_ID}",
661                           "VERSION_ID", *latestVersionId);
662                 invokeActivation(activation);
663                 break;
664             }
665         }
666     }
667 }
668 
669 void
invokeActivation(const std::unique_ptr<Activation> & activation)670     ItemUpdater::invokeActivation(const std::unique_ptr<Activation>& activation)
671 {
672     activation->requestedActivation(Activation::RequestedActivations::Active);
673 }
674 
onPSUInterfacesAdded(sdbusplus::message_t & msg)675 void ItemUpdater::onPSUInterfacesAdded(sdbusplus::message_t& msg)
676 {
677     // Maintain static set of valid PSU paths. This is needed if PSU interface
678     // comes in a separate InterfacesAdded message from Item interface.
679     static std::set<std::string> psuPaths{};
680 
681     try
682     {
683         sdbusplus::message::object_path objPath;
684         InterfacesAddedMap interfaces;
685         msg.read(objPath, interfaces);
686         std::string path = objPath.str;
687 
688         if (interfaces.contains(PSU_INVENTORY_IFACE))
689         {
690             psuPaths.insert(path);
691         }
692 
693         if (interfaces.contains(ITEM_IFACE) && psuPaths.contains(path) &&
694             !psuStatusMap.contains(path))
695         {
696             auto interface = interfaces[ITEM_IFACE];
697             if (interface.contains(PRESENT))
698             {
699                 addPsuToStatusMap(path);
700                 psuStatusMap[path].present = std::get<bool>(interface[PRESENT]);
701                 handlePSUPresenceChanged(path);
702                 if (psuStatusMap[path].present)
703                 {
704                     // Check if there are new PSU images to update
705                     processStoredImage();
706                     syncToLatestImage();
707                 }
708             }
709         }
710     }
711     catch (const std::exception& e)
712     {
713         lg2::error("Unable to handle inventory InterfacesAdded event: {ERROR}",
714                    "ERROR", e);
715     }
716 }
717 
processPSUImageAndSyncToLatest()718 void ItemUpdater::processPSUImageAndSyncToLatest()
719 {
720     processPSUImage();
721     processStoredImage();
722     syncToLatestImage();
723 }
724 
getFWVersionFromBuiltinDir()725 std::string ItemUpdater::getFWVersionFromBuiltinDir()
726 {
727     std::string version;
728     for (const auto& activation : activations)
729     {
730         if (activation.second->path().starts_with(IMG_DIR_BUILTIN))
731         {
732             std::string versionId = activation.second->getVersionId();
733             auto it = versions.find(versionId);
734             if (it != versions.end())
735             {
736                 const auto& versionPtr = it->second;
737                 version = versionPtr->version();
738                 break;
739             }
740         }
741     }
742     return version;
743 }
744 
745 } // namespace updater
746 } // namespace software
747 } // namespace phosphor
748