blob: 6f774cbd51eb68061f66642a5ef55ddecc77887d [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 YU01539e72019-07-31 10:57:38 +08007#include <phosphor-logging/elog-errors.hpp>
8#include <phosphor-logging/log.hpp>
9#include <xyz/openbmc_project/Common/error.hpp>
10
Patrick Williams5670b182023-05-10 07:50:50 -050011#include <cassert>
12#include <filesystem>
13
Lei YUfda15a32019-09-19 14:43:02 +080014namespace
15{
Lei YU58c26e32019-09-27 17:52:06 +080016constexpr auto MANIFEST_VERSION = "version";
17constexpr auto MANIFEST_EXTENDED_VERSION = "extended_version";
18} // namespace
Lei YUfda15a32019-09-19 14:43:02 +080019
Lei YU01539e72019-07-31 10:57:38 +080020namespace phosphor
21{
22namespace software
23{
24namespace updater
25{
26namespace server = sdbusplus::xyz::openbmc_project::Software::server;
Lei YU01539e72019-07-31 10:57:38 +080027
28using namespace sdbusplus::xyz::openbmc_project::Common::Error;
29using namespace phosphor::logging;
Lei YUad90ad52019-08-06 11:19:28 +080030using SVersion = server::Version;
31using VersionPurpose = SVersion::VersionPurpose;
Lei YU01539e72019-07-31 10:57:38 +080032
Patrick Williams374fae52022-07-22 19:26:55 -050033void ItemUpdater::createActivation(sdbusplus::message_t& m)
Lei YU01539e72019-07-31 10:57:38 +080034{
Lei YU01539e72019-07-31 10:57:38 +080035 sdbusplus::message::object_path objPath;
George Liude270292020-06-12 17:14:17 +080036 std::map<std::string, std::map<std::string, std::variant<std::string>>>
Lei YU01539e72019-07-31 10:57:38 +080037 interfaces;
38 m.read(objPath, interfaces);
39
40 std::string path(std::move(objPath));
41 std::string filePath;
42 auto purpose = VersionPurpose::Unknown;
43 std::string version;
44
45 for (const auto& [interfaceName, propertyMap] : interfaces)
46 {
47 if (interfaceName == VERSION_IFACE)
48 {
49 for (const auto& [propertyName, propertyValue] : propertyMap)
50 {
51 if (propertyName == "Purpose")
52 {
53 // Only process the PSU images
54 auto value = SVersion::convertVersionPurposeFromString(
Lei YU1517f5f2019-10-14 16:44:42 +080055 std::get<std::string>(propertyValue));
Lei YU01539e72019-07-31 10:57:38 +080056
57 if (value == VersionPurpose::PSU)
58 {
59 purpose = value;
60 }
61 }
Lei YUf77189f2019-08-07 14:26:30 +080062 else if (propertyName == VERSION)
Lei YU01539e72019-07-31 10:57:38 +080063 {
Lei YU1517f5f2019-10-14 16:44:42 +080064 version = std::get<std::string>(propertyValue);
Lei YU01539e72019-07-31 10:57:38 +080065 }
66 }
67 }
68 else if (interfaceName == FILEPATH_IFACE)
69 {
70 const auto& it = propertyMap.find("Path");
71 if (it != propertyMap.end())
72 {
Lei YU1517f5f2019-10-14 16:44:42 +080073 filePath = std::get<std::string>(it->second);
Lei YU01539e72019-07-31 10:57:38 +080074 }
75 }
76 }
77 if ((filePath.empty()) || (purpose == VersionPurpose::Unknown))
78 {
79 return;
80 }
81
82 // Version id is the last item in the path
83 auto pos = path.rfind("/");
84 if (pos == std::string::npos)
85 {
86 log<level::ERR>("No version id found in object path",
87 entry("OBJPATH=%s", path.c_str()));
88 return;
89 }
90
91 auto versionId = path.substr(pos + 1);
92
93 if (activations.find(versionId) == activations.end())
94 {
95 // Determine the Activation state by processing the given image dir.
Lei YU91029442019-08-01 15:57:31 +080096 AssociationList associations;
Lei YU58c26e32019-09-27 17:52:06 +080097 auto activationState = Activation::Status::Ready;
Lei YU01539e72019-07-31 10:57:38 +080098
Lei YU91029442019-08-01 15:57:31 +080099 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
100 ACTIVATION_REV_ASSOCIATION,
Lei YU5e0dcb32019-08-02 18:04:34 +0800101 PSU_INVENTORY_PATH_BASE));
Lei YU91029442019-08-01 15:57:31 +0800102
Lei YU01539e72019-07-31 10:57:38 +0800103 fs::path manifestPath(filePath);
104 manifestPath /= MANIFEST_FILE;
Lei YU58c26e32019-09-27 17:52:06 +0800105 std::string extendedVersion =
106 Version::getValue(manifestPath, {MANIFEST_EXTENDED_VERSION});
Lei YU01539e72019-07-31 10:57:38 +0800107
Lei YU99301372019-09-29 16:27:12 +0800108 auto activation =
109 createActivationObject(path, versionId, extendedVersion,
110 activationState, associations, filePath);
Lei YU01539e72019-07-31 10:57:38 +0800111 activations.emplace(versionId, std::move(activation));
112
Patrick Williams5670b182023-05-10 07:50:50 -0500113 auto versionPtr = createVersionObject(path, versionId, version,
114 purpose);
Lei YU01539e72019-07-31 10:57:38 +0800115 versions.emplace(versionId, std::move(versionPtr));
116 }
117 return;
118}
119
Lei YUa5c47bb2019-09-29 11:28:53 +0800120void ItemUpdater::erase(const std::string& versionId)
Lei YU01539e72019-07-31 10:57:38 +0800121{
122 auto it = versions.find(versionId);
123 if (it == versions.end())
124 {
125 log<level::ERR>(("Error: Failed to find version " + versionId +
126 " in item updater versions map."
127 " Unable to remove.")
128 .c_str());
129 }
130 else
131 {
Lei YU1517f5f2019-10-14 16:44:42 +0800132 versionStrings.erase(it->second->getVersionString());
133 versions.erase(it);
Lei YU01539e72019-07-31 10:57:38 +0800134 }
135
136 // Removing entry in activations map
137 auto ita = activations.find(versionId);
138 if (ita == activations.end())
139 {
140 log<level::ERR>(("Error: Failed to find version " + versionId +
141 " in item updater activations map."
142 " Unable to remove.")
143 .c_str());
144 }
145 else
146 {
147 activations.erase(versionId);
148 }
149}
150
Lei YU91029442019-08-01 15:57:31 +0800151void ItemUpdater::createActiveAssociation(const std::string& path)
152{
153 assocs.emplace_back(
154 std::make_tuple(ACTIVE_FWD_ASSOCIATION, ACTIVE_REV_ASSOCIATION, path));
155 associations(assocs);
156}
157
Lei YUad90ad52019-08-06 11:19:28 +0800158void ItemUpdater::addFunctionalAssociation(const std::string& path)
Lei YU91029442019-08-01 15:57:31 +0800159{
Lei YU91029442019-08-01 15:57:31 +0800160 assocs.emplace_back(std::make_tuple(FUNCTIONAL_FWD_ASSOCIATION,
161 FUNCTIONAL_REV_ASSOCIATION, path));
162 associations(assocs);
163}
164
Lei YUa8b966f2020-03-18 10:32:24 +0800165void ItemUpdater::addUpdateableAssociation(const std::string& path)
166{
167 assocs.emplace_back(std::make_tuple(UPDATEABLE_FWD_ASSOCIATION,
168 UPDATEABLE_REV_ASSOCIATION, path));
169 associations(assocs);
170}
171
Lei YU91029442019-08-01 15:57:31 +0800172void ItemUpdater::removeAssociation(const std::string& path)
173{
174 for (auto iter = assocs.begin(); iter != assocs.end();)
175 {
176 if ((std::get<2>(*iter)).compare(path) == 0)
177 {
178 iter = assocs.erase(iter);
179 associations(assocs);
180 }
181 else
182 {
183 ++iter;
184 }
185 }
186}
187
Lei YUffb36532019-10-15 13:55:24 +0800188void ItemUpdater::onUpdateDone(const std::string& versionId,
189 const std::string& psuInventoryPath)
190{
191 // After update is done, remove old activation objects
192 for (auto it = activations.begin(); it != activations.end(); ++it)
193 {
194 if (it->second->getVersionId() != versionId &&
195 utils::isAssociated(psuInventoryPath, it->second->associations()))
196 {
197 removePsuObject(psuInventoryPath);
198 break;
199 }
200 }
Lei YU1517f5f2019-10-14 16:44:42 +0800201
202 auto it = activations.find(versionId);
203 assert(it != activations.end());
204 psuPathActivationMap.emplace(psuInventoryPath, it->second);
Lei YUffb36532019-10-15 13:55:24 +0800205}
206
Lei YU01539e72019-07-31 10:57:38 +0800207std::unique_ptr<Activation> ItemUpdater::createActivationObject(
208 const std::string& path, const std::string& versionId,
Lei YU58c26e32019-09-27 17:52:06 +0800209 const std::string& extVersion, Activation::Status activationStatus,
Lei YU99301372019-09-29 16:27:12 +0800210 const AssociationList& assocs, const std::string& filePath)
Lei YU01539e72019-07-31 10:57:38 +0800211{
212 return std::make_unique<Activation>(bus, path, versionId, extVersion,
Lei YUffb36532019-10-15 13:55:24 +0800213 activationStatus, assocs, filePath,
214 this, this);
Lei YU01539e72019-07-31 10:57:38 +0800215}
216
Lei YUad90ad52019-08-06 11:19:28 +0800217void ItemUpdater::createPsuObject(const std::string& psuInventoryPath,
218 const std::string& psuVersion)
219{
220 auto versionId = utils::getVersionId(psuVersion);
221 auto path = std::string(SOFTWARE_OBJPATH) + "/" + versionId;
222
223 auto it = activations.find(versionId);
224 if (it != activations.end())
225 {
226 // The versionId is already created, associate the path
227 auto associations = it->second->associations();
228 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
229 ACTIVATION_REV_ASSOCIATION,
230 psuInventoryPath));
231 it->second->associations(associations);
Lei YUbd3b0072019-08-08 13:09:50 +0800232 psuPathActivationMap.emplace(psuInventoryPath, it->second);
Lei YUad90ad52019-08-06 11:19:28 +0800233 }
234 else
235 {
236 // Create a new object for running PSU inventory
237 AssociationList associations;
Lei YU58c26e32019-09-27 17:52:06 +0800238 auto activationState = Activation::Status::Active;
Lei YUad90ad52019-08-06 11:19:28 +0800239
240 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
241 ACTIVATION_REV_ASSOCIATION,
242 psuInventoryPath));
243
Lei YU99301372019-09-29 16:27:12 +0800244 auto activation = createActivationObject(
245 path, versionId, "", activationState, associations, "");
Lei YUad90ad52019-08-06 11:19:28 +0800246 activations.emplace(versionId, std::move(activation));
Lei YUbd3b0072019-08-08 13:09:50 +0800247 psuPathActivationMap.emplace(psuInventoryPath, activations[versionId]);
Lei YUad90ad52019-08-06 11:19:28 +0800248
249 auto versionPtr = createVersionObject(path, versionId, psuVersion,
Lei YU99301372019-09-29 16:27:12 +0800250 VersionPurpose::PSU);
Lei YUad90ad52019-08-06 11:19:28 +0800251 versions.emplace(versionId, std::move(versionPtr));
252
253 createActiveAssociation(path);
254 addFunctionalAssociation(path);
Lei YUa8b966f2020-03-18 10:32:24 +0800255 addUpdateableAssociation(path);
Lei YUad90ad52019-08-06 11:19:28 +0800256 }
257}
258
Lei YUbd3b0072019-08-08 13:09:50 +0800259void ItemUpdater::removePsuObject(const std::string& psuInventoryPath)
260{
Lei YU1517f5f2019-10-14 16:44:42 +0800261 psuStatusMap[psuInventoryPath] = {false, ""};
262
Lei YUbd3b0072019-08-08 13:09:50 +0800263 auto it = psuPathActivationMap.find(psuInventoryPath);
264 if (it == psuPathActivationMap.end())
265 {
266 log<level::ERR>("No Activation found for PSU",
267 entry("PSUPATH=%s", psuInventoryPath.c_str()));
268 return;
269 }
270 const auto& activationPtr = it->second;
271 psuPathActivationMap.erase(psuInventoryPath);
272
273 auto associations = activationPtr->associations();
274 for (auto iter = associations.begin(); iter != associations.end();)
275 {
276 if ((std::get<2>(*iter)).compare(psuInventoryPath) == 0)
277 {
278 iter = associations.erase(iter);
279 }
280 else
281 {
282 ++iter;
283 }
284 }
285 if (associations.empty())
286 {
287 // Remove the activation
Lei YUa5c47bb2019-09-29 11:28:53 +0800288 erase(activationPtr->getVersionId());
Lei YUbd3b0072019-08-08 13:09:50 +0800289 }
290 else
291 {
292 // Update association
293 activationPtr->associations(associations);
294 }
295}
296
Lei YU01539e72019-07-31 10:57:38 +0800297std::unique_ptr<Version> ItemUpdater::createVersionObject(
298 const std::string& objPath, const std::string& versionId,
299 const std::string& versionString,
300 sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose
Lei YU99301372019-09-29 16:27:12 +0800301 versionPurpose)
Lei YU01539e72019-07-31 10:57:38 +0800302{
Lei YU65207482019-10-11 16:39:36 +0800303 versionStrings.insert(versionString);
Lei YU01539e72019-07-31 10:57:38 +0800304 auto version = std::make_unique<Version>(
Lei YU99301372019-09-29 16:27:12 +0800305 bus, objPath, versionId, versionString, versionPurpose,
Lei YU01539e72019-07-31 10:57:38 +0800306 std::bind(&ItemUpdater::erase, this, std::placeholders::_1));
307 return version;
308}
309
Patrick Williams374fae52022-07-22 19:26:55 -0500310void ItemUpdater::onPsuInventoryChangedMsg(sdbusplus::message_t& msg)
Lei YUad90ad52019-08-06 11:19:28 +0800311{
Lei YUbd3b0072019-08-08 13:09:50 +0800312 using Interface = std::string;
Lei YUbd3b0072019-08-08 13:09:50 +0800313 Interface interface;
314 Properties properties;
Lei YUbd3b0072019-08-08 13:09:50 +0800315 std::string psuPath = msg.get_path();
316
317 msg.read(interface, properties);
Lei YUa2c2cd72019-08-09 15:54:10 +0800318 onPsuInventoryChanged(psuPath, properties);
319}
320
321void ItemUpdater::onPsuInventoryChanged(const std::string& psuPath,
322 const Properties& properties)
323{
Lei YU1517f5f2019-10-14 16:44:42 +0800324 std::optional<bool> present;
325 std::optional<std::string> model;
Lei YUbd3b0072019-08-08 13:09:50 +0800326
Lei YU1517f5f2019-10-14 16:44:42 +0800327 // The code was expecting to get callback on multiple properties changed.
328 // But in practice, the callback is received one-by-one for each property.
329 // So it has to handle Present and Version property separately.
Lei YUf77189f2019-08-07 14:26:30 +0800330 auto p = properties.find(PRESENT);
Lei YU1517f5f2019-10-14 16:44:42 +0800331 if (p != properties.end())
332 {
333 present = std::get<bool>(p->second);
334 psuStatusMap[psuPath].present = *present;
335 }
336 p = properties.find(MODEL);
337 if (p != properties.end())
338 {
339 model = std::get<std::string>(p->second);
340 psuStatusMap[psuPath].model = *model;
341 }
342
343 // If present or model is not changed, ignore
344 if (!present.has_value() && !model.has_value())
Lei YUbd3b0072019-08-08 13:09:50 +0800345 {
346 return;
347 }
348
Lei YU1517f5f2019-10-14 16:44:42 +0800349 if (psuStatusMap[psuPath].present)
Lei YUbd3b0072019-08-08 13:09:50 +0800350 {
Lei YU1517f5f2019-10-14 16:44:42 +0800351 // If model is not updated, let's wait for it
352 if (psuStatusMap[psuPath].model.empty())
353 {
354 log<level::DEBUG>("Waiting for model to be updated");
355 return;
356 }
357
358 auto version = utils::getVersion(psuPath);
Lei YUdcaf8932019-09-09 16:09:35 +0800359 if (!version.empty())
Lei YUbd3b0072019-08-08 13:09:50 +0800360 {
Lei YUdcaf8932019-09-09 16:09:35 +0800361 createPsuObject(psuPath, version);
Lei YU1517f5f2019-10-14 16:44:42 +0800362 // Check if there is new PSU images to update
363 syncToLatestImage();
364 }
365 else
366 {
367 // TODO: log an event
368 log<level::ERR>("Failed to get PSU version",
369 entry("PSU=%s", psuPath.c_str()));
Lei YUbd3b0072019-08-08 13:09:50 +0800370 }
Lei YUbd3b0072019-08-08 13:09:50 +0800371 }
372 else
373 {
Lei YU1517f5f2019-10-14 16:44:42 +0800374 if (!present.has_value())
375 {
376 // If a PSU is plugged out, model property is update to empty as
377 // well, and we get callback here, but ignore that because it is
378 // handled by "Present" callback.
379 return;
380 }
Lei YUbd3b0072019-08-08 13:09:50 +0800381 // Remove object or association
382 removePsuObject(psuPath);
383 }
Lei YUad90ad52019-08-06 11:19:28 +0800384}
385
386void ItemUpdater::processPSUImage()
387{
388 auto paths = utils::getPSUInventoryPath(bus);
389 for (const auto& p : paths)
390 {
Lei YU5f3584d2019-08-27 16:28:53 +0800391 auto service = utils::getService(bus, p.c_str(), ITEM_IFACE);
Lei YUad90ad52019-08-06 11:19:28 +0800392 auto present = utils::getProperty<bool>(bus, service.c_str(), p.c_str(),
Lei YUf77189f2019-08-07 14:26:30 +0800393 ITEM_IFACE, PRESENT);
Lei YU5f3584d2019-08-27 16:28:53 +0800394 auto version = utils::getVersion(p);
Lei YUad90ad52019-08-06 11:19:28 +0800395 if (present && !version.empty())
396 {
397 createPsuObject(p, version);
398 }
Lei YUbd3b0072019-08-08 13:09:50 +0800399 // Add matches for PSU Inventory's property changes
400 psuMatches.emplace_back(
Lei YUdcaf8932019-09-09 16:09:35 +0800401 bus, MatchRules::propertiesChanged(p, ITEM_IFACE),
Lei YUa2c2cd72019-08-09 15:54:10 +0800402 std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this,
Lei YU1517f5f2019-10-14 16:44:42 +0800403 std::placeholders::_1)); // For present
404 psuMatches.emplace_back(
405 bus, MatchRules::propertiesChanged(p, ASSET_IFACE),
406 std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this,
407 std::placeholders::_1)); // For model
Lei YUad90ad52019-08-06 11:19:28 +0800408 }
409}
410
Lei YU58c26e32019-09-27 17:52:06 +0800411void ItemUpdater::processStoredImage()
412{
413 scanDirectory(IMG_DIR_BUILTIN);
414 scanDirectory(IMG_DIR_PERSIST);
415}
416
417void ItemUpdater::scanDirectory(const fs::path& dir)
418{
419 // The directory shall put PSU images in directories named with model
420 if (!fs::exists(dir))
421 {
422 // Skip
423 return;
424 }
425 if (!fs::is_directory(dir))
426 {
427 log<level::ERR>("The path is not a directory",
428 entry("PATH=%s", dir.c_str()));
429 return;
430 }
431 for (const auto& d : fs::directory_iterator(dir))
432 {
433 // If the model in manifest does not match the dir name
434 // Log a warning and skip it
435 auto path = d.path();
436 auto manifest = path / MANIFEST_FILE;
437 if (fs::exists(manifest))
438 {
439 auto ret = Version::getValues(
440 manifest.string(),
441 {MANIFEST_VERSION, MANIFEST_EXTENDED_VERSION});
442 auto version = ret[MANIFEST_VERSION];
443 auto extVersion = ret[MANIFEST_EXTENDED_VERSION];
444 auto info = Version::getExtVersionInfo(extVersion);
445 auto model = info["model"];
446 if (path.stem() != model)
447 {
448 log<level::ERR>("Unmatched model",
449 entry("PATH=%s", path.c_str()),
450 entry("MODEL=%s", model.c_str()));
451 continue;
452 }
453 auto versionId = utils::getVersionId(version);
454 auto it = activations.find(versionId);
455 if (it == activations.end())
456 {
457 // This is a version that is different than the running PSUs
458 auto activationState = Activation::Status::Ready;
459 auto purpose = VersionPurpose::PSU;
460 auto objPath = std::string(SOFTWARE_OBJPATH) + "/" + versionId;
461
462 auto activation = createActivationObject(
463 objPath, versionId, extVersion, activationState, {}, path);
464 activations.emplace(versionId, std::move(activation));
465
Patrick Williams5670b182023-05-10 07:50:50 -0500466 auto versionPtr = createVersionObject(objPath, versionId,
467 version, purpose);
Lei YU58c26e32019-09-27 17:52:06 +0800468 versions.emplace(versionId, std::move(versionPtr));
469 }
470 else
471 {
472 // This is a version that a running PSU is using, set the path
473 // on the version object
474 it->second->path(path);
475 }
476 }
477 else
478 {
479 log<level::ERR>("No MANIFEST found",
480 entry("PATH=%s", path.c_str()));
481 }
482 }
483}
484
Lei YU65207482019-10-11 16:39:36 +0800485std::optional<std::string> ItemUpdater::getLatestVersionId()
486{
487 auto latestVersion = utils::getLatestVersion(versionStrings);
488 if (latestVersion.empty())
489 {
490 return {};
491 }
492
493 std::optional<std::string> versionId;
494 for (const auto& v : versions)
495 {
496 if (v.second->version() == latestVersion)
497 {
498 versionId = v.first;
499 break;
500 }
501 }
502 assert(versionId.has_value());
503 return versionId;
504}
505
Lei YU63f9e712019-10-12 15:16:55 +0800506void ItemUpdater::syncToLatestImage()
507{
508 auto latestVersionId = getLatestVersionId();
509 if (!latestVersionId)
510 {
511 return;
512 }
513 const auto& it = activations.find(*latestVersionId);
514 assert(it != activations.end());
515 const auto& activation = it->second;
516 const auto& assocs = activation->associations();
517
518 auto paths = utils::getPSUInventoryPath(bus);
519 for (const auto& p : paths)
520 {
521 // As long as there is a PSU is not associated with the latest image,
522 // run the activation so that all PSUs are running the same latest
523 // image.
524 if (!utils::isAssociated(p, assocs))
525 {
526 log<level::INFO>("Automatically update PSU",
527 entry("VERSION_ID=%s", latestVersionId->c_str()));
528 invokeActivation(activation);
529 break;
530 }
531 }
532}
533
534void ItemUpdater::invokeActivation(
535 const std::unique_ptr<Activation>& activation)
536{
537 activation->requestedActivation(Activation::RequestedActivations::Active);
538}
539
Lei YU01539e72019-07-31 10:57:38 +0800540} // namespace updater
541} // namespace software
542} // namespace phosphor