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 std::unique_ptr<Activation> ItemUpdater::createActivationObject(
183     const std::string& path, const std::string& versionId,
184     const std::string& extVersion, Activation::Status activationStatus,
185     const AssociationList& assocs, const std::string& filePath)
186 {
187     return std::make_unique<Activation>(bus, path, versionId, extVersion,
188                                         activationStatus, assocs, this,
189                                         filePath);
190 }
191 
192 void ItemUpdater::createPsuObject(const std::string& psuInventoryPath,
193                                   const std::string& psuVersion)
194 {
195     auto versionId = utils::getVersionId(psuVersion);
196     auto path = std::string(SOFTWARE_OBJPATH) + "/" + versionId;
197 
198     auto it = activations.find(versionId);
199     if (it != activations.end())
200     {
201         // The versionId is already created, associate the path
202         auto associations = it->second->associations();
203         associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
204                                                   ACTIVATION_REV_ASSOCIATION,
205                                                   psuInventoryPath));
206         it->second->associations(associations);
207         psuPathActivationMap.emplace(psuInventoryPath, it->second);
208     }
209     else
210     {
211         // Create a new object for running PSU inventory
212         AssociationList associations;
213         auto activationState = Activation::Status::Active;
214 
215         associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
216                                                   ACTIVATION_REV_ASSOCIATION,
217                                                   psuInventoryPath));
218 
219         auto activation = createActivationObject(
220             path, versionId, "", activationState, associations, "");
221         activations.emplace(versionId, std::move(activation));
222         psuPathActivationMap.emplace(psuInventoryPath, activations[versionId]);
223 
224         auto versionPtr = createVersionObject(path, versionId, psuVersion,
225                                               VersionPurpose::PSU);
226         versions.emplace(versionId, std::move(versionPtr));
227 
228         createActiveAssociation(path);
229         addFunctionalAssociation(path);
230     }
231 }
232 
233 void ItemUpdater::removePsuObject(const std::string& psuInventoryPath)
234 {
235     auto it = psuPathActivationMap.find(psuInventoryPath);
236     if (it == psuPathActivationMap.end())
237     {
238         log<level::ERR>("No Activation found for PSU",
239                         entry("PSUPATH=%s", psuInventoryPath.c_str()));
240         return;
241     }
242     const auto& activationPtr = it->second;
243     psuPathActivationMap.erase(psuInventoryPath);
244 
245     auto associations = activationPtr->associations();
246     for (auto iter = associations.begin(); iter != associations.end();)
247     {
248         if ((std::get<2>(*iter)).compare(psuInventoryPath) == 0)
249         {
250             iter = associations.erase(iter);
251         }
252         else
253         {
254             ++iter;
255         }
256     }
257     if (associations.empty())
258     {
259         // Remove the activation
260         erase(activationPtr->getVersionId());
261     }
262     else
263     {
264         // Update association
265         activationPtr->associations(associations);
266     }
267 }
268 
269 std::unique_ptr<Version> ItemUpdater::createVersionObject(
270     const std::string& objPath, const std::string& versionId,
271     const std::string& versionString,
272     sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose
273         versionPurpose)
274 {
275     versionStrings.insert(versionString);
276     auto version = std::make_unique<Version>(
277         bus, objPath, versionId, versionString, versionPurpose,
278         std::bind(&ItemUpdater::erase, this, std::placeholders::_1));
279     return version;
280 }
281 
282 void ItemUpdater::onPsuInventoryChangedMsg(sdbusplus::message::message& msg)
283 {
284     using Interface = std::string;
285     Interface interface;
286     Properties properties;
287     std::string psuPath = msg.get_path();
288 
289     msg.read(interface, properties);
290     onPsuInventoryChanged(psuPath, properties);
291 }
292 
293 void ItemUpdater::onPsuInventoryChanged(const std::string& psuPath,
294                                         const Properties& properties)
295 {
296     bool present;
297     std::string version;
298 
299     // Only present property is interested
300     auto p = properties.find(PRESENT);
301     if (p == properties.end())
302     {
303         return;
304     }
305     present = sdbusplus::message::variant_ns::get<bool>(p->second);
306 
307     if (present)
308     {
309         version = utils::getVersion(psuPath);
310         if (!version.empty())
311         {
312             createPsuObject(psuPath, version);
313         }
314     }
315     else
316     {
317         // Remove object or association
318         removePsuObject(psuPath);
319     }
320 }
321 
322 void ItemUpdater::processPSUImage()
323 {
324     auto paths = utils::getPSUInventoryPath(bus);
325     for (const auto& p : paths)
326     {
327         auto service = utils::getService(bus, p.c_str(), ITEM_IFACE);
328         auto present = utils::getProperty<bool>(bus, service.c_str(), p.c_str(),
329                                                 ITEM_IFACE, PRESENT);
330         auto version = utils::getVersion(p);
331         if (present && !version.empty())
332         {
333             createPsuObject(p, version);
334         }
335         // Add matches for PSU Inventory's property changes
336         psuMatches.emplace_back(
337             bus, MatchRules::propertiesChanged(p, ITEM_IFACE),
338             std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this,
339                       std::placeholders::_1));
340     }
341 }
342 
343 void ItemUpdater::processStoredImage()
344 {
345     scanDirectory(IMG_DIR_BUILTIN);
346     scanDirectory(IMG_DIR_PERSIST);
347 }
348 
349 void ItemUpdater::scanDirectory(const fs::path& dir)
350 {
351     // The directory shall put PSU images in directories named with model
352     if (!fs::exists(dir))
353     {
354         // Skip
355         return;
356     }
357     if (!fs::is_directory(dir))
358     {
359         log<level::ERR>("The path is not a directory",
360                         entry("PATH=%s", dir.c_str()));
361         return;
362     }
363     for (const auto& d : fs::directory_iterator(dir))
364     {
365         // If the model in manifest does not match the dir name
366         // Log a warning and skip it
367         auto path = d.path();
368         auto manifest = path / MANIFEST_FILE;
369         if (fs::exists(manifest))
370         {
371             auto ret = Version::getValues(
372                 manifest.string(),
373                 {MANIFEST_VERSION, MANIFEST_EXTENDED_VERSION});
374             auto version = ret[MANIFEST_VERSION];
375             auto extVersion = ret[MANIFEST_EXTENDED_VERSION];
376             auto info = Version::getExtVersionInfo(extVersion);
377             auto model = info["model"];
378             if (path.stem() != model)
379             {
380                 log<level::ERR>("Unmatched model",
381                                 entry("PATH=%s", path.c_str()),
382                                 entry("MODEL=%s", model.c_str()));
383                 continue;
384             }
385             auto versionId = utils::getVersionId(version);
386             auto it = activations.find(versionId);
387             if (it == activations.end())
388             {
389                 // This is a version that is different than the running PSUs
390                 auto activationState = Activation::Status::Ready;
391                 auto purpose = VersionPurpose::PSU;
392                 auto objPath = std::string(SOFTWARE_OBJPATH) + "/" + versionId;
393 
394                 auto activation = createActivationObject(
395                     objPath, versionId, extVersion, activationState, {}, path);
396                 activations.emplace(versionId, std::move(activation));
397 
398                 auto versionPtr =
399                     createVersionObject(objPath, versionId, version, purpose);
400                 versions.emplace(versionId, std::move(versionPtr));
401             }
402             else
403             {
404                 // This is a version that a running PSU is using, set the path
405                 // on the version object
406                 it->second->path(path);
407             }
408         }
409         else
410         {
411             log<level::ERR>("No MANIFEST found",
412                             entry("PATH=%s", path.c_str()));
413         }
414     }
415 }
416 
417 std::optional<std::string> ItemUpdater::getLatestVersionId()
418 {
419     auto latestVersion = utils::getLatestVersion(versionStrings);
420     if (latestVersion.empty())
421     {
422         return {};
423     }
424 
425     std::optional<std::string> versionId;
426     for (const auto& v : versions)
427     {
428         if (v.second->version() == latestVersion)
429         {
430             versionId = v.first;
431             break;
432         }
433     }
434     assert(versionId.has_value());
435     return versionId;
436 }
437 
438 void ItemUpdater::syncToLatestImage()
439 {
440     auto latestVersionId = getLatestVersionId();
441     if (!latestVersionId)
442     {
443         return;
444     }
445     const auto& it = activations.find(*latestVersionId);
446     assert(it != activations.end());
447     const auto& activation = it->second;
448     const auto& assocs = activation->associations();
449 
450     auto paths = utils::getPSUInventoryPath(bus);
451     for (const auto& p : paths)
452     {
453         // As long as there is a PSU is not associated with the latest image,
454         // run the activation so that all PSUs are running the same latest
455         // image.
456         if (!utils::isAssociated(p, assocs))
457         {
458             log<level::INFO>("Automatically update PSU",
459                              entry("VERSION_ID=%s", latestVersionId->c_str()));
460             invokeActivation(activation);
461             break;
462         }
463     }
464 }
465 
466 void ItemUpdater::invokeActivation(
467     const std::unique_ptr<Activation>& activation)
468 {
469     activation->requestedActivation(Activation::RequestedActivations::Active);
470 }
471 
472 } // namespace updater
473 } // namespace software
474 } // namespace phosphor
475