1 #include "config.h"
2 
3 #include "item_updater.hpp"
4 
5 #include "utils.hpp"
6 
7 #include <cassert>
8 #include <filesystem>
9 #include <phosphor-logging/elog-errors.hpp>
10 #include <phosphor-logging/log.hpp>
11 #include <xyz/openbmc_project/Common/error.hpp>
12 
13 namespace
14 {
15 constexpr auto MANIFEST_VERSION = "version";
16 constexpr auto MANIFEST_EXTENDED_VERSION = "extended_version";
17 } // namespace
18 
19 namespace phosphor
20 {
21 namespace software
22 {
23 namespace updater
24 {
25 namespace server = sdbusplus::xyz::openbmc_project::Software::server;
26 
27 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
28 using namespace phosphor::logging;
29 using SVersion = server::Version;
30 using VersionPurpose = SVersion::VersionPurpose;
31 
32 void ItemUpdater::createActivation(sdbusplus::message::message& m)
33 {
34     namespace msg = sdbusplus::message;
35     namespace variant_ns = msg::variant_ns;
36 
37     sdbusplus::message::object_path objPath;
38     std::map<std::string, std::map<std::string, msg::variant<std::string>>>
39         interfaces;
40     m.read(objPath, interfaces);
41 
42     std::string path(std::move(objPath));
43     std::string filePath;
44     auto purpose = VersionPurpose::Unknown;
45     std::string version;
46 
47     for (const auto& [interfaceName, propertyMap] : interfaces)
48     {
49         if (interfaceName == VERSION_IFACE)
50         {
51             for (const auto& [propertyName, propertyValue] : propertyMap)
52             {
53                 if (propertyName == "Purpose")
54                 {
55                     // Only process the PSU images
56                     auto value = SVersion::convertVersionPurposeFromString(
57                         variant_ns::get<std::string>(propertyValue));
58 
59                     if (value == VersionPurpose::PSU)
60                     {
61                         purpose = value;
62                     }
63                 }
64                 else if (propertyName == VERSION)
65                 {
66                     version = variant_ns::get<std::string>(propertyValue);
67                 }
68             }
69         }
70         else if (interfaceName == FILEPATH_IFACE)
71         {
72             const auto& it = propertyMap.find("Path");
73             if (it != propertyMap.end())
74             {
75                 filePath = variant_ns::get<std::string>(it->second);
76             }
77         }
78     }
79     if ((filePath.empty()) || (purpose == VersionPurpose::Unknown))
80     {
81         return;
82     }
83 
84     // Version id is the last item in the path
85     auto pos = path.rfind("/");
86     if (pos == std::string::npos)
87     {
88         log<level::ERR>("No version id found in object path",
89                         entry("OBJPATH=%s", path.c_str()));
90         return;
91     }
92 
93     auto versionId = path.substr(pos + 1);
94 
95     if (activations.find(versionId) == activations.end())
96     {
97         // Determine the Activation state by processing the given image dir.
98         AssociationList associations;
99         auto activationState = Activation::Status::Ready;
100 
101         associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
102                                                   ACTIVATION_REV_ASSOCIATION,
103                                                   PSU_INVENTORY_PATH_BASE));
104 
105         fs::path manifestPath(filePath);
106         manifestPath /= MANIFEST_FILE;
107         std::string extendedVersion =
108             Version::getValue(manifestPath, {MANIFEST_EXTENDED_VERSION});
109 
110         auto activation =
111             createActivationObject(path, versionId, extendedVersion,
112                                    activationState, associations, filePath);
113         activations.emplace(versionId, std::move(activation));
114 
115         auto versionPtr =
116             createVersionObject(path, versionId, version, purpose);
117         versions.emplace(versionId, std::move(versionPtr));
118     }
119     return;
120 }
121 
122 void ItemUpdater::erase(const std::string& versionId)
123 {
124     auto it = versions.find(versionId);
125     if (it == versions.end())
126     {
127         log<level::ERR>(("Error: Failed to find version " + versionId +
128                          " in item updater versions map."
129                          " Unable to remove.")
130                             .c_str());
131     }
132     else
133     {
134         versions.erase(versionId);
135     }
136 
137     // Removing entry in activations map
138     auto ita = activations.find(versionId);
139     if (ita == activations.end())
140     {
141         log<level::ERR>(("Error: Failed to find version " + versionId +
142                          " in item updater activations map."
143                          " Unable to remove.")
144                             .c_str());
145     }
146     else
147     {
148         activations.erase(versionId);
149     }
150 }
151 
152 void ItemUpdater::createActiveAssociation(const std::string& path)
153 {
154     assocs.emplace_back(
155         std::make_tuple(ACTIVE_FWD_ASSOCIATION, ACTIVE_REV_ASSOCIATION, path));
156     associations(assocs);
157 }
158 
159 void ItemUpdater::addFunctionalAssociation(const std::string& path)
160 {
161     assocs.emplace_back(std::make_tuple(FUNCTIONAL_FWD_ASSOCIATION,
162                                         FUNCTIONAL_REV_ASSOCIATION, path));
163     associations(assocs);
164 }
165 
166 void ItemUpdater::removeAssociation(const std::string& path)
167 {
168     for (auto iter = assocs.begin(); iter != assocs.end();)
169     {
170         if ((std::get<2>(*iter)).compare(path) == 0)
171         {
172             iter = assocs.erase(iter);
173             associations(assocs);
174         }
175         else
176         {
177             ++iter;
178         }
179     }
180 }
181 
182 void ItemUpdater::onUpdateDone(const std::string& versionId,
183                                const std::string& psuInventoryPath)
184 {
185     // After update is done, remove old activation objects
186     for (auto it = activations.begin(); it != activations.end(); ++it)
187     {
188         if (it->second->getVersionId() != versionId &&
189             utils::isAssociated(psuInventoryPath, it->second->associations()))
190         {
191             removePsuObject(psuInventoryPath);
192             break;
193         }
194     }
195 }
196 
197 std::unique_ptr<Activation> ItemUpdater::createActivationObject(
198     const std::string& path, const std::string& versionId,
199     const std::string& extVersion, Activation::Status activationStatus,
200     const AssociationList& assocs, const std::string& filePath)
201 {
202     return std::make_unique<Activation>(bus, path, versionId, extVersion,
203                                         activationStatus, assocs, filePath,
204                                         this, this);
205 }
206 
207 void ItemUpdater::createPsuObject(const std::string& psuInventoryPath,
208                                   const std::string& psuVersion)
209 {
210     auto versionId = utils::getVersionId(psuVersion);
211     auto path = std::string(SOFTWARE_OBJPATH) + "/" + versionId;
212 
213     auto it = activations.find(versionId);
214     if (it != activations.end())
215     {
216         // The versionId is already created, associate the path
217         auto associations = it->second->associations();
218         associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
219                                                   ACTIVATION_REV_ASSOCIATION,
220                                                   psuInventoryPath));
221         it->second->associations(associations);
222         psuPathActivationMap.emplace(psuInventoryPath, it->second);
223     }
224     else
225     {
226         // Create a new object for running PSU inventory
227         AssociationList associations;
228         auto activationState = Activation::Status::Active;
229 
230         associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
231                                                   ACTIVATION_REV_ASSOCIATION,
232                                                   psuInventoryPath));
233 
234         auto activation = createActivationObject(
235             path, versionId, "", activationState, associations, "");
236         activations.emplace(versionId, std::move(activation));
237         psuPathActivationMap.emplace(psuInventoryPath, activations[versionId]);
238 
239         auto versionPtr = createVersionObject(path, versionId, psuVersion,
240                                               VersionPurpose::PSU);
241         versions.emplace(versionId, std::move(versionPtr));
242 
243         createActiveAssociation(path);
244         addFunctionalAssociation(path);
245     }
246 }
247 
248 void ItemUpdater::removePsuObject(const std::string& psuInventoryPath)
249 {
250     auto it = psuPathActivationMap.find(psuInventoryPath);
251     if (it == psuPathActivationMap.end())
252     {
253         log<level::ERR>("No Activation found for PSU",
254                         entry("PSUPATH=%s", psuInventoryPath.c_str()));
255         return;
256     }
257     const auto& activationPtr = it->second;
258     psuPathActivationMap.erase(psuInventoryPath);
259 
260     auto associations = activationPtr->associations();
261     for (auto iter = associations.begin(); iter != associations.end();)
262     {
263         if ((std::get<2>(*iter)).compare(psuInventoryPath) == 0)
264         {
265             iter = associations.erase(iter);
266         }
267         else
268         {
269             ++iter;
270         }
271     }
272     if (associations.empty())
273     {
274         // Remove the activation
275         erase(activationPtr->getVersionId());
276     }
277     else
278     {
279         // Update association
280         activationPtr->associations(associations);
281     }
282 }
283 
284 std::unique_ptr<Version> ItemUpdater::createVersionObject(
285     const std::string& objPath, const std::string& versionId,
286     const std::string& versionString,
287     sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose
288         versionPurpose)
289 {
290     versionStrings.insert(versionString);
291     auto version = std::make_unique<Version>(
292         bus, objPath, versionId, versionString, versionPurpose,
293         std::bind(&ItemUpdater::erase, this, std::placeholders::_1));
294     return version;
295 }
296 
297 void ItemUpdater::onPsuInventoryChangedMsg(sdbusplus::message::message& msg)
298 {
299     using Interface = std::string;
300     Interface interface;
301     Properties properties;
302     std::string psuPath = msg.get_path();
303 
304     msg.read(interface, properties);
305     onPsuInventoryChanged(psuPath, properties);
306 }
307 
308 void ItemUpdater::onPsuInventoryChanged(const std::string& psuPath,
309                                         const Properties& properties)
310 {
311     bool present;
312     std::string version;
313 
314     // Only present property is interested
315     auto p = properties.find(PRESENT);
316     if (p == properties.end())
317     {
318         return;
319     }
320     present = sdbusplus::message::variant_ns::get<bool>(p->second);
321 
322     if (present)
323     {
324         version = utils::getVersion(psuPath);
325         if (!version.empty())
326         {
327             createPsuObject(psuPath, version);
328         }
329     }
330     else
331     {
332         // Remove object or association
333         removePsuObject(psuPath);
334     }
335 }
336 
337 void ItemUpdater::processPSUImage()
338 {
339     auto paths = utils::getPSUInventoryPath(bus);
340     for (const auto& p : paths)
341     {
342         auto service = utils::getService(bus, p.c_str(), ITEM_IFACE);
343         auto present = utils::getProperty<bool>(bus, service.c_str(), p.c_str(),
344                                                 ITEM_IFACE, PRESENT);
345         auto version = utils::getVersion(p);
346         if (present && !version.empty())
347         {
348             createPsuObject(p, version);
349         }
350         // Add matches for PSU Inventory's property changes
351         psuMatches.emplace_back(
352             bus, MatchRules::propertiesChanged(p, ITEM_IFACE),
353             std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this,
354                       std::placeholders::_1));
355     }
356 }
357 
358 void ItemUpdater::processStoredImage()
359 {
360     scanDirectory(IMG_DIR_BUILTIN);
361     scanDirectory(IMG_DIR_PERSIST);
362 }
363 
364 void ItemUpdater::scanDirectory(const fs::path& dir)
365 {
366     // The directory shall put PSU images in directories named with model
367     if (!fs::exists(dir))
368     {
369         // Skip
370         return;
371     }
372     if (!fs::is_directory(dir))
373     {
374         log<level::ERR>("The path is not a directory",
375                         entry("PATH=%s", dir.c_str()));
376         return;
377     }
378     for (const auto& d : fs::directory_iterator(dir))
379     {
380         // If the model in manifest does not match the dir name
381         // Log a warning and skip it
382         auto path = d.path();
383         auto manifest = path / MANIFEST_FILE;
384         if (fs::exists(manifest))
385         {
386             auto ret = Version::getValues(
387                 manifest.string(),
388                 {MANIFEST_VERSION, MANIFEST_EXTENDED_VERSION});
389             auto version = ret[MANIFEST_VERSION];
390             auto extVersion = ret[MANIFEST_EXTENDED_VERSION];
391             auto info = Version::getExtVersionInfo(extVersion);
392             auto model = info["model"];
393             if (path.stem() != model)
394             {
395                 log<level::ERR>("Unmatched model",
396                                 entry("PATH=%s", path.c_str()),
397                                 entry("MODEL=%s", model.c_str()));
398                 continue;
399             }
400             auto versionId = utils::getVersionId(version);
401             auto it = activations.find(versionId);
402             if (it == activations.end())
403             {
404                 // This is a version that is different than the running PSUs
405                 auto activationState = Activation::Status::Ready;
406                 auto purpose = VersionPurpose::PSU;
407                 auto objPath = std::string(SOFTWARE_OBJPATH) + "/" + versionId;
408 
409                 auto activation = createActivationObject(
410                     objPath, versionId, extVersion, activationState, {}, path);
411                 activations.emplace(versionId, std::move(activation));
412 
413                 auto versionPtr =
414                     createVersionObject(objPath, versionId, version, purpose);
415                 versions.emplace(versionId, std::move(versionPtr));
416             }
417             else
418             {
419                 // This is a version that a running PSU is using, set the path
420                 // on the version object
421                 it->second->path(path);
422             }
423         }
424         else
425         {
426             log<level::ERR>("No MANIFEST found",
427                             entry("PATH=%s", path.c_str()));
428         }
429     }
430 }
431 
432 std::optional<std::string> ItemUpdater::getLatestVersionId()
433 {
434     auto latestVersion = utils::getLatestVersion(versionStrings);
435     if (latestVersion.empty())
436     {
437         return {};
438     }
439 
440     std::optional<std::string> versionId;
441     for (const auto& v : versions)
442     {
443         if (v.second->version() == latestVersion)
444         {
445             versionId = v.first;
446             break;
447         }
448     }
449     assert(versionId.has_value());
450     return versionId;
451 }
452 
453 void ItemUpdater::syncToLatestImage()
454 {
455     auto latestVersionId = getLatestVersionId();
456     if (!latestVersionId)
457     {
458         return;
459     }
460     const auto& it = activations.find(*latestVersionId);
461     assert(it != activations.end());
462     const auto& activation = it->second;
463     const auto& assocs = activation->associations();
464 
465     auto paths = utils::getPSUInventoryPath(bus);
466     for (const auto& p : paths)
467     {
468         // As long as there is a PSU is not associated with the latest image,
469         // run the activation so that all PSUs are running the same latest
470         // image.
471         if (!utils::isAssociated(p, assocs))
472         {
473             log<level::INFO>("Automatically update PSU",
474                              entry("VERSION_ID=%s", latestVersionId->c_str()));
475             invokeActivation(activation);
476             break;
477         }
478     }
479 }
480 
481 void ItemUpdater::invokeActivation(
482     const std::unique_ptr<Activation>& activation)
483 {
484     activation->requestedActivation(Activation::RequestedActivations::Active);
485 }
486 
487 } // namespace updater
488 } // namespace software
489 } // namespace phosphor
490