blob: 6d4e4c2cf5e609144a095253ea70989ff95d06cd [file] [log] [blame]
Lei YU01539e72019-07-31 10:57:38 +08001#include "config.h"
2
3#include "item_updater.hpp"
4
Lei YUad90ad52019-08-06 11:19:28 +08005#include "utils.hpp"
6
Lei YU65207482019-10-11 16:39:36 +08007#include <cassert>
Lei YU01539e72019-07-31 10:57:38 +08008#include <filesystem>
9#include <phosphor-logging/elog-errors.hpp>
10#include <phosphor-logging/log.hpp>
11#include <xyz/openbmc_project/Common/error.hpp>
12
Lei YUfda15a32019-09-19 14:43:02 +080013namespace
14{
Lei YU58c26e32019-09-27 17:52:06 +080015constexpr auto MANIFEST_VERSION = "version";
16constexpr auto MANIFEST_EXTENDED_VERSION = "extended_version";
17} // namespace
Lei YUfda15a32019-09-19 14:43:02 +080018
Lei YU01539e72019-07-31 10:57:38 +080019namespace phosphor
20{
21namespace software
22{
23namespace updater
24{
25namespace server = sdbusplus::xyz::openbmc_project::Software::server;
Lei YU01539e72019-07-31 10:57:38 +080026
27using namespace sdbusplus::xyz::openbmc_project::Common::Error;
28using namespace phosphor::logging;
Lei YUad90ad52019-08-06 11:19:28 +080029using SVersion = server::Version;
30using VersionPurpose = SVersion::VersionPurpose;
Lei YU01539e72019-07-31 10:57:38 +080031
32void ItemUpdater::createActivation(sdbusplus::message::message& m)
33{
Lei YU01539e72019-07-31 10:57:38 +080034 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 }
Lei YUf77189f2019-08-07 14:26:30 +080064 else if (propertyName == VERSION)
Lei YU01539e72019-07-31 10:57:38 +080065 {
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.
Lei YU91029442019-08-01 15:57:31 +080098 AssociationList associations;
Lei YU58c26e32019-09-27 17:52:06 +080099 auto activationState = Activation::Status::Ready;
Lei YU01539e72019-07-31 10:57:38 +0800100
Lei YU91029442019-08-01 15:57:31 +0800101 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
102 ACTIVATION_REV_ASSOCIATION,
Lei YU5e0dcb32019-08-02 18:04:34 +0800103 PSU_INVENTORY_PATH_BASE));
Lei YU91029442019-08-01 15:57:31 +0800104
Lei YU01539e72019-07-31 10:57:38 +0800105 fs::path manifestPath(filePath);
106 manifestPath /= MANIFEST_FILE;
Lei YU58c26e32019-09-27 17:52:06 +0800107 std::string extendedVersion =
108 Version::getValue(manifestPath, {MANIFEST_EXTENDED_VERSION});
Lei YU01539e72019-07-31 10:57:38 +0800109
Lei YU99301372019-09-29 16:27:12 +0800110 auto activation =
111 createActivationObject(path, versionId, extendedVersion,
112 activationState, associations, filePath);
Lei YU01539e72019-07-31 10:57:38 +0800113 activations.emplace(versionId, std::move(activation));
114
115 auto versionPtr =
Lei YU99301372019-09-29 16:27:12 +0800116 createVersionObject(path, versionId, version, purpose);
Lei YU01539e72019-07-31 10:57:38 +0800117 versions.emplace(versionId, std::move(versionPtr));
118 }
119 return;
120}
121
Lei YUa5c47bb2019-09-29 11:28:53 +0800122void ItemUpdater::erase(const std::string& versionId)
Lei YU01539e72019-07-31 10:57:38 +0800123{
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
Lei YU91029442019-08-01 15:57:31 +0800152void 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
Lei YUad90ad52019-08-06 11:19:28 +0800159void ItemUpdater::addFunctionalAssociation(const std::string& path)
Lei YU91029442019-08-01 15:57:31 +0800160{
Lei YU91029442019-08-01 15:57:31 +0800161 assocs.emplace_back(std::make_tuple(FUNCTIONAL_FWD_ASSOCIATION,
162 FUNCTIONAL_REV_ASSOCIATION, path));
163 associations(assocs);
164}
165
166void 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
Lei YUffb36532019-10-15 13:55:24 +0800182void 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
Lei YU01539e72019-07-31 10:57:38 +0800197std::unique_ptr<Activation> ItemUpdater::createActivationObject(
198 const std::string& path, const std::string& versionId,
Lei YU58c26e32019-09-27 17:52:06 +0800199 const std::string& extVersion, Activation::Status activationStatus,
Lei YU99301372019-09-29 16:27:12 +0800200 const AssociationList& assocs, const std::string& filePath)
Lei YU01539e72019-07-31 10:57:38 +0800201{
202 return std::make_unique<Activation>(bus, path, versionId, extVersion,
Lei YUffb36532019-10-15 13:55:24 +0800203 activationStatus, assocs, filePath,
204 this, this);
Lei YU01539e72019-07-31 10:57:38 +0800205}
206
Lei YUad90ad52019-08-06 11:19:28 +0800207void 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);
Lei YUbd3b0072019-08-08 13:09:50 +0800222 psuPathActivationMap.emplace(psuInventoryPath, it->second);
Lei YUad90ad52019-08-06 11:19:28 +0800223 }
224 else
225 {
226 // Create a new object for running PSU inventory
227 AssociationList associations;
Lei YU58c26e32019-09-27 17:52:06 +0800228 auto activationState = Activation::Status::Active;
Lei YUad90ad52019-08-06 11:19:28 +0800229
230 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
231 ACTIVATION_REV_ASSOCIATION,
232 psuInventoryPath));
233
Lei YU99301372019-09-29 16:27:12 +0800234 auto activation = createActivationObject(
235 path, versionId, "", activationState, associations, "");
Lei YUad90ad52019-08-06 11:19:28 +0800236 activations.emplace(versionId, std::move(activation));
Lei YUbd3b0072019-08-08 13:09:50 +0800237 psuPathActivationMap.emplace(psuInventoryPath, activations[versionId]);
Lei YUad90ad52019-08-06 11:19:28 +0800238
239 auto versionPtr = createVersionObject(path, versionId, psuVersion,
Lei YU99301372019-09-29 16:27:12 +0800240 VersionPurpose::PSU);
Lei YUad90ad52019-08-06 11:19:28 +0800241 versions.emplace(versionId, std::move(versionPtr));
242
243 createActiveAssociation(path);
244 addFunctionalAssociation(path);
245 }
246}
247
Lei YUbd3b0072019-08-08 13:09:50 +0800248void ItemUpdater::removePsuObject(const std::string& psuInventoryPath)
249{
Lei YUbd3b0072019-08-08 13:09:50 +0800250 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
Lei YUa5c47bb2019-09-29 11:28:53 +0800275 erase(activationPtr->getVersionId());
Lei YUbd3b0072019-08-08 13:09:50 +0800276 }
277 else
278 {
279 // Update association
280 activationPtr->associations(associations);
281 }
282}
283
Lei YU01539e72019-07-31 10:57:38 +0800284std::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
Lei YU99301372019-09-29 16:27:12 +0800288 versionPurpose)
Lei YU01539e72019-07-31 10:57:38 +0800289{
Lei YU65207482019-10-11 16:39:36 +0800290 versionStrings.insert(versionString);
Lei YU01539e72019-07-31 10:57:38 +0800291 auto version = std::make_unique<Version>(
Lei YU99301372019-09-29 16:27:12 +0800292 bus, objPath, versionId, versionString, versionPurpose,
Lei YU01539e72019-07-31 10:57:38 +0800293 std::bind(&ItemUpdater::erase, this, std::placeholders::_1));
294 return version;
295}
296
Lei YUa2c2cd72019-08-09 15:54:10 +0800297void ItemUpdater::onPsuInventoryChangedMsg(sdbusplus::message::message& msg)
Lei YUad90ad52019-08-06 11:19:28 +0800298{
Lei YUbd3b0072019-08-08 13:09:50 +0800299 using Interface = std::string;
Lei YUbd3b0072019-08-08 13:09:50 +0800300 Interface interface;
301 Properties properties;
Lei YUbd3b0072019-08-08 13:09:50 +0800302 std::string psuPath = msg.get_path();
303
304 msg.read(interface, properties);
Lei YUa2c2cd72019-08-09 15:54:10 +0800305 onPsuInventoryChanged(psuPath, properties);
306}
307
308void ItemUpdater::onPsuInventoryChanged(const std::string& psuPath,
309 const Properties& properties)
310{
Lei YUdcaf8932019-09-09 16:09:35 +0800311 bool present;
312 std::string version;
Lei YUbd3b0072019-08-08 13:09:50 +0800313
Lei YUdcaf8932019-09-09 16:09:35 +0800314 // Only present property is interested
Lei YUf77189f2019-08-07 14:26:30 +0800315 auto p = properties.find(PRESENT);
Lei YUdcaf8932019-09-09 16:09:35 +0800316 if (p == properties.end())
Lei YUbd3b0072019-08-08 13:09:50 +0800317 {
318 return;
319 }
Lei YUdcaf8932019-09-09 16:09:35 +0800320 present = sdbusplus::message::variant_ns::get<bool>(p->second);
Lei YUbd3b0072019-08-08 13:09:50 +0800321
Lei YUdcaf8932019-09-09 16:09:35 +0800322 if (present)
Lei YUbd3b0072019-08-08 13:09:50 +0800323 {
Lei YUdcaf8932019-09-09 16:09:35 +0800324 version = utils::getVersion(psuPath);
325 if (!version.empty())
Lei YUbd3b0072019-08-08 13:09:50 +0800326 {
Lei YUdcaf8932019-09-09 16:09:35 +0800327 createPsuObject(psuPath, version);
Lei YUbd3b0072019-08-08 13:09:50 +0800328 }
Lei YUbd3b0072019-08-08 13:09:50 +0800329 }
330 else
331 {
Lei YUbd3b0072019-08-08 13:09:50 +0800332 // Remove object or association
333 removePsuObject(psuPath);
334 }
Lei YUad90ad52019-08-06 11:19:28 +0800335}
336
337void ItemUpdater::processPSUImage()
338{
339 auto paths = utils::getPSUInventoryPath(bus);
340 for (const auto& p : paths)
341 {
Lei YU5f3584d2019-08-27 16:28:53 +0800342 auto service = utils::getService(bus, p.c_str(), ITEM_IFACE);
Lei YUad90ad52019-08-06 11:19:28 +0800343 auto present = utils::getProperty<bool>(bus, service.c_str(), p.c_str(),
Lei YUf77189f2019-08-07 14:26:30 +0800344 ITEM_IFACE, PRESENT);
Lei YU5f3584d2019-08-27 16:28:53 +0800345 auto version = utils::getVersion(p);
Lei YUad90ad52019-08-06 11:19:28 +0800346 if (present && !version.empty())
347 {
348 createPsuObject(p, version);
349 }
Lei YUbd3b0072019-08-08 13:09:50 +0800350 // Add matches for PSU Inventory's property changes
351 psuMatches.emplace_back(
Lei YUdcaf8932019-09-09 16:09:35 +0800352 bus, MatchRules::propertiesChanged(p, ITEM_IFACE),
Lei YUa2c2cd72019-08-09 15:54:10 +0800353 std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this,
Lei YUbd3b0072019-08-08 13:09:50 +0800354 std::placeholders::_1));
Lei YUad90ad52019-08-06 11:19:28 +0800355 }
356}
357
Lei YU58c26e32019-09-27 17:52:06 +0800358void ItemUpdater::processStoredImage()
359{
360 scanDirectory(IMG_DIR_BUILTIN);
361 scanDirectory(IMG_DIR_PERSIST);
362}
363
364void 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
Lei YU65207482019-10-11 16:39:36 +0800432std::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
Lei YU63f9e712019-10-12 15:16:55 +0800453void 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
481void ItemUpdater::invokeActivation(
482 const std::unique_ptr<Activation>& activation)
483{
484 activation->requestedActivation(Activation::RequestedActivations::Active);
485}
486
Lei YU01539e72019-07-31 10:57:38 +0800487} // namespace updater
488} // namespace software
489} // namespace phosphor