blob: 62d21450c86a94bb6a1fa8be0509026d4b50312e [file] [log] [blame]
Lei YU01539e72019-07-31 10:57:38 +08001#include "config.h"
2
3#include "item_updater.hpp"
4
Shawn McCarney487e2e12024-11-25 17:19:46 -06005#include "runtime_warning.hpp"
Lei YUad90ad52019-08-06 11:19:28 +08006#include "utils.hpp"
7
Lei YU01539e72019-07-31 10:57:38 +08008#include <phosphor-logging/elog-errors.hpp>
Shawn McCarneycdf86de2024-11-26 10:02:14 -06009#include <phosphor-logging/lg2.hpp>
Lei YU01539e72019-07-31 10:57:38 +080010#include <xyz/openbmc_project/Common/error.hpp>
11
Shawn McCarney487e2e12024-11-25 17:19:46 -060012#include <exception>
Patrick Williams5670b182023-05-10 07:50:50 -050013#include <filesystem>
Shawn McCarney783406e2024-11-17 21:49:37 -060014#include <format>
15#include <set>
Shawn McCarney487e2e12024-11-25 17:19:46 -060016#include <stdexcept>
Patrick Williams5670b182023-05-10 07:50:50 -050017
Lei YUfda15a32019-09-19 14:43:02 +080018namespace
19{
Lei YU58c26e32019-09-27 17:52:06 +080020constexpr auto MANIFEST_VERSION = "version";
21constexpr auto MANIFEST_EXTENDED_VERSION = "extended_version";
22} // namespace
Lei YUfda15a32019-09-19 14:43:02 +080023
Lei YU01539e72019-07-31 10:57:38 +080024namespace phosphor
25{
26namespace software
27{
28namespace updater
29{
30namespace server = sdbusplus::xyz::openbmc_project::Software::server;
Lei YU01539e72019-07-31 10:57:38 +080031
32using namespace sdbusplus::xyz::openbmc_project::Common::Error;
33using namespace phosphor::logging;
Lei YUad90ad52019-08-06 11:19:28 +080034using SVersion = server::Version;
35using VersionPurpose = SVersion::VersionPurpose;
Lei YU01539e72019-07-31 10:57:38 +080036
Shawn McCarney487e2e12024-11-25 17:19:46 -060037void ItemUpdater::onVersionInterfacesAddedMsg(sdbusplus::message_t& msg)
Lei YU01539e72019-07-31 10:57:38 +080038{
Shawn McCarney487e2e12024-11-25 17:19:46 -060039 try
40 {
41 sdbusplus::message::object_path objPath;
42 InterfacesAddedMap interfaces;
43 msg.read(objPath, interfaces);
Lei YU01539e72019-07-31 10:57:38 +080044
Shawn McCarney487e2e12024-11-25 17:19:46 -060045 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
55void ItemUpdater::onVersionInterfacesAdded(const std::string& path,
56 const InterfacesAddedMap& interfaces)
57{
Lei YU01539e72019-07-31 10:57:38 +080058 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(
Lei YU1517f5f2019-10-14 16:44:42 +080072 std::get<std::string>(propertyValue));
Lei YU01539e72019-07-31 10:57:38 +080073
74 if (value == VersionPurpose::PSU)
75 {
76 purpose = value;
77 }
78 }
Lei YUf77189f2019-08-07 14:26:30 +080079 else if (propertyName == VERSION)
Lei YU01539e72019-07-31 10:57:38 +080080 {
Lei YU1517f5f2019-10-14 16:44:42 +080081 version = std::get<std::string>(propertyValue);
Lei YU01539e72019-07-31 10:57:38 +080082 }
83 }
84 }
85 else if (interfaceName == FILEPATH_IFACE)
86 {
87 const auto& it = propertyMap.find("Path");
88 if (it != propertyMap.end())
89 {
Lei YU1517f5f2019-10-14 16:44:42 +080090 filePath = std::get<std::string>(it->second);
Lei YU01539e72019-07-31 10:57:38 +080091 }
92 }
93 }
94 if ((filePath.empty()) || (purpose == VersionPurpose::Unknown))
95 {
96 return;
97 }
98
Shawn McCarney799f5142024-09-26 14:50:25 -050099 // 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
Lei YU01539e72019-07-31 10:57:38 +0800106 // Version id is the last item in the path
George Liua0f2cf72024-08-23 14:50:12 +0800107 auto pos = path.rfind('/');
Lei YU01539e72019-07-31 10:57:38 +0800108 if (pos == std::string::npos)
109 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600110 lg2::error("No version id found in object path {OBJPATH}", "OBJPATH",
111 path);
Lei YU01539e72019-07-31 10:57:38 +0800112 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.
Lei YU91029442019-08-01 15:57:31 +0800120 AssociationList associations;
Lei YU58c26e32019-09-27 17:52:06 +0800121 auto activationState = Activation::Status::Ready;
Lei YU01539e72019-07-31 10:57:38 +0800122
Patrick Williamsbab5ed92024-08-16 15:20:54 -0400123 associations.emplace_back(std::make_tuple(
124 ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION,
125 PSU_INVENTORY_PATH_BASE));
Lei YU91029442019-08-01 15:57:31 +0800126
Lei YU01539e72019-07-31 10:57:38 +0800127 fs::path manifestPath(filePath);
128 manifestPath /= MANIFEST_FILE;
Lei YU58c26e32019-09-27 17:52:06 +0800129 std::string extendedVersion =
130 Version::getValue(manifestPath, {MANIFEST_EXTENDED_VERSION});
Lei YU01539e72019-07-31 10:57:38 +0800131
Lei YU99301372019-09-29 16:27:12 +0800132 auto activation =
133 createActivationObject(path, versionId, extendedVersion,
134 activationState, associations, filePath);
Lei YU01539e72019-07-31 10:57:38 +0800135 activations.emplace(versionId, std::move(activation));
136
Patrick Williamsbab5ed92024-08-16 15:20:54 -0400137 auto versionPtr =
138 createVersionObject(path, versionId, version, purpose);
Lei YU01539e72019-07-31 10:57:38 +0800139 versions.emplace(versionId, std::move(versionPtr));
140 }
Lei YU01539e72019-07-31 10:57:38 +0800141}
142
Lei YUa5c47bb2019-09-29 11:28:53 +0800143void ItemUpdater::erase(const std::string& versionId)
Lei YU01539e72019-07-31 10:57:38 +0800144{
145 auto it = versions.find(versionId);
146 if (it == versions.end())
147 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600148 lg2::error("Error: Failed to find version {VERSION_ID} in "
149 "item updater versions map. Unable to remove.",
150 "VERSION_ID", versionId);
Lei YU01539e72019-07-31 10:57:38 +0800151 }
152 else
153 {
Lei YU1517f5f2019-10-14 16:44:42 +0800154 versionStrings.erase(it->second->getVersionString());
155 versions.erase(it);
Lei YU01539e72019-07-31 10:57:38 +0800156 }
157
158 // Removing entry in activations map
159 auto ita = activations.find(versionId);
160 if (ita == activations.end())
161 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600162 lg2::error("Error: Failed to find version {VERSION_ID} in "
163 "item updater activations map. Unable to remove.",
164 "VERSION_ID", versionId);
Lei YU01539e72019-07-31 10:57:38 +0800165 }
166 else
167 {
168 activations.erase(versionId);
169 }
170}
171
Lei YU91029442019-08-01 15:57:31 +0800172void 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
Lei YUad90ad52019-08-06 11:19:28 +0800179void ItemUpdater::addFunctionalAssociation(const std::string& path)
Lei YU91029442019-08-01 15:57:31 +0800180{
Lei YU91029442019-08-01 15:57:31 +0800181 assocs.emplace_back(std::make_tuple(FUNCTIONAL_FWD_ASSOCIATION,
182 FUNCTIONAL_REV_ASSOCIATION, path));
183 associations(assocs);
184}
185
Lei YUa8b966f2020-03-18 10:32:24 +0800186void 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
Lei YU91029442019-08-01 15:57:31 +0800193void ItemUpdater::removeAssociation(const std::string& path)
194{
195 for (auto iter = assocs.begin(); iter != assocs.end();)
196 {
George Liua5205e42024-08-23 15:27:54 +0800197 if ((std::get<2>(*iter)) == path)
Lei YU91029442019-08-01 15:57:31 +0800198 {
199 iter = assocs.erase(iter);
200 associations(assocs);
201 }
202 else
203 {
204 ++iter;
205 }
206 }
207}
208
Lei YUffb36532019-10-15 13:55:24 +0800209void 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 }
Lei YU1517f5f2019-10-14 16:44:42 +0800222
223 auto it = activations.find(versionId);
Shawn McCarney487e2e12024-11-25 17:19:46 -0600224 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 }
Lei YUffb36532019-10-15 13:55:24 +0800233}
234
Lei YU01539e72019-07-31 10:57:38 +0800235std::unique_ptr<Activation> ItemUpdater::createActivationObject(
236 const std::string& path, const std::string& versionId,
Lei YU58c26e32019-09-27 17:52:06 +0800237 const std::string& extVersion, Activation::Status activationStatus,
Lei YU99301372019-09-29 16:27:12 +0800238 const AssociationList& assocs, const std::string& filePath)
Lei YU01539e72019-07-31 10:57:38 +0800239{
240 return std::make_unique<Activation>(bus, path, versionId, extVersion,
Lei YUffb36532019-10-15 13:55:24 +0800241 activationStatus, assocs, filePath,
242 this, this);
Lei YU01539e72019-07-31 10:57:38 +0800243}
244
Lei YUad90ad52019-08-06 11:19:28 +0800245void 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();
Patrick Williamsbab5ed92024-08-16 15:20:54 -0400256 associations.emplace_back(
257 std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
258 ACTIVATION_REV_ASSOCIATION, psuInventoryPath));
Lei YUad90ad52019-08-06 11:19:28 +0800259 it->second->associations(associations);
Lei YUbd3b0072019-08-08 13:09:50 +0800260 psuPathActivationMap.emplace(psuInventoryPath, it->second);
Lei YUad90ad52019-08-06 11:19:28 +0800261 }
262 else
263 {
264 // Create a new object for running PSU inventory
265 AssociationList associations;
Lei YU58c26e32019-09-27 17:52:06 +0800266 auto activationState = Activation::Status::Active;
Lei YUad90ad52019-08-06 11:19:28 +0800267
Patrick Williamsbab5ed92024-08-16 15:20:54 -0400268 associations.emplace_back(
269 std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
270 ACTIVATION_REV_ASSOCIATION, psuInventoryPath));
Lei YUad90ad52019-08-06 11:19:28 +0800271
Lei YU99301372019-09-29 16:27:12 +0800272 auto activation = createActivationObject(
273 path, versionId, "", activationState, associations, "");
Lei YUad90ad52019-08-06 11:19:28 +0800274 activations.emplace(versionId, std::move(activation));
Lei YUbd3b0072019-08-08 13:09:50 +0800275 psuPathActivationMap.emplace(psuInventoryPath, activations[versionId]);
Lei YUad90ad52019-08-06 11:19:28 +0800276
277 auto versionPtr = createVersionObject(path, versionId, psuVersion,
Lei YU99301372019-09-29 16:27:12 +0800278 VersionPurpose::PSU);
Lei YUad90ad52019-08-06 11:19:28 +0800279 versions.emplace(versionId, std::move(versionPtr));
280
281 createActiveAssociation(path);
282 addFunctionalAssociation(path);
Lei YUa8b966f2020-03-18 10:32:24 +0800283 addUpdateableAssociation(path);
Lei YUad90ad52019-08-06 11:19:28 +0800284 }
285}
286
Lei YUbd3b0072019-08-08 13:09:50 +0800287void ItemUpdater::removePsuObject(const std::string& psuInventoryPath)
288{
Lei YUbd3b0072019-08-08 13:09:50 +0800289 auto it = psuPathActivationMap.find(psuInventoryPath);
290 if (it == psuPathActivationMap.end())
291 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600292 lg2::error("No Activation found for PSU {PSUPATH}", "PSUPATH",
293 psuInventoryPath);
Lei YUbd3b0072019-08-08 13:09:50 +0800294 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 {
George Liua5205e42024-08-23 15:27:54 +0800302 if ((std::get<2>(*iter)) == psuInventoryPath)
Lei YUbd3b0072019-08-08 13:09:50 +0800303 {
304 iter = associations.erase(iter);
305 }
306 else
307 {
308 ++iter;
309 }
310 }
311 if (associations.empty())
312 {
313 // Remove the activation
Lei YUa5c47bb2019-09-29 11:28:53 +0800314 erase(activationPtr->getVersionId());
Lei YUbd3b0072019-08-08 13:09:50 +0800315 }
316 else
317 {
318 // Update association
319 activationPtr->associations(associations);
320 }
321}
322
Shawn McCarney73a6f0d2024-10-30 16:11:29 -0500323void ItemUpdater::addPsuToStatusMap(const std::string& psuPath)
324{
325 if (!psuStatusMap.contains(psuPath))
326 {
327 psuStatusMap[psuPath] = {false, ""};
328
Shawn McCarney783406e2024-11-17 21:49:37 -0600329 // Add PropertiesChanged listener for Item interface so we are notified
330 // when Present property changes
Shawn McCarney73a6f0d2024-10-30 16:11:29 -0500331 psuMatches.emplace_back(
332 bus, MatchRules::propertiesChanged(psuPath, ITEM_IFACE),
333 std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this,
Shawn McCarney783406e2024-11-17 21:49:37 -0600334 std::placeholders::_1));
335 }
336}
337
338void 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 }
Shawn McCarney73a6f0d2024-10-30 16:11:29 -0500361 }
362}
363
Lei YU01539e72019-07-31 10:57:38 +0800364std::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
Lei YU99301372019-09-29 16:27:12 +0800368 versionPurpose)
Lei YU01539e72019-07-31 10:57:38 +0800369{
Lei YU65207482019-10-11 16:39:36 +0800370 versionStrings.insert(versionString);
Lei YU01539e72019-07-31 10:57:38 +0800371 auto version = std::make_unique<Version>(
Lei YU99301372019-09-29 16:27:12 +0800372 bus, objPath, versionId, versionString, versionPurpose,
Lei YU01539e72019-07-31 10:57:38 +0800373 std::bind(&ItemUpdater::erase, this, std::placeholders::_1));
374 return version;
375}
376
Patrick Williams374fae52022-07-22 19:26:55 -0500377void ItemUpdater::onPsuInventoryChangedMsg(sdbusplus::message_t& msg)
Lei YUad90ad52019-08-06 11:19:28 +0800378{
Shawn McCarney783406e2024-11-17 21:49:37 -0600379 try
Lei YU1517f5f2019-10-14 16:44:42 +0800380 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600381 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);
Lei YUbd3b0072019-08-08 13:09:50 +0800388 }
Shawn McCarney783406e2024-11-17 21:49:37 -0600389 catch (const std::exception& e)
Lei YUbd3b0072019-08-08 13:09:50 +0800390 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600391 lg2::error(
392 "Unable to handle inventory PropertiesChanged event: {ERROR}",
393 "ERROR", e);
Lei YUbd3b0072019-08-08 13:09:50 +0800394 }
Lei YUad90ad52019-08-06 11:19:28 +0800395}
396
Shawn McCarney487e2e12024-11-25 17:19:46 -0600397void 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
Lei YUad90ad52019-08-06 11:19:28 +0800413void ItemUpdater::processPSUImage()
414{
Shawn McCarney783406e2024-11-17 21:49:37 -0600415 try
Lei YUad90ad52019-08-06 11:19:28 +0800416 {
Shawn McCarneyd57bd2f2024-12-02 18:40:28 -0600417 auto paths = utils::getPSUInventoryPaths(bus);
Shawn McCarney783406e2024-11-17 21:49:37 -0600418 for (const auto& p : paths)
Lei YUad90ad52019-08-06 11:19:28 +0800419 {
Shawn McCarney783406e2024-11-17 21:49:37 -0600420 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 }
Lei YUad90ad52019-08-06 11:19:28 +0800432 }
Lei YUad90ad52019-08-06 11:19:28 +0800433 }
Shawn McCarney783406e2024-11-17 21:49:37 -0600434 catch (const std::exception& e)
435 {
436 // Ignore errors; the information might not be available yet
437 }
Lei YUad90ad52019-08-06 11:19:28 +0800438}
439
Lei YU58c26e32019-09-27 17:52:06 +0800440void ItemUpdater::processStoredImage()
441{
Shawn McCarney487e2e12024-11-25 17:19:46 -0600442 // Build list of directories to scan
443 std::vector<fs::path> paths;
444 paths.emplace_back(IMG_DIR_BUILTIN);
Faisal Awadafb86e792024-09-11 10:51:17 -0500445 if (!ALWAYS_USE_BUILTIN_IMG_DIR)
446 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600447 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 }
Faisal Awadafb86e792024-09-11 10:51:17 -0500466 }
Lei YU58c26e32019-09-27 17:52:06 +0800467}
468
469void ItemUpdater::scanDirectory(const fs::path& dir)
470{
Shawn McCarney487e2e12024-11-25 17:19:46 -0600471 // Find the model subdirectory within the specified directory
472 auto modelDir = findModelDirectory(dir);
473 if (modelDir.empty())
Lei YU58c26e32019-09-27 17:52:06 +0800474 {
Lei YU58c26e32019-09-27 17:52:06 +0800475 return;
476 }
Faisal Awada760053d2024-05-16 13:31:32 -0500477
Shawn McCarney487e2e12024-11-25 17:19:46 -0600478 // 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 // This is a version that a running PSU is using, set the path
539 // on the version object
540 it->second->path(modelDir);
541 }
542}
543
544fs::path ItemUpdater::findModelDirectory(const fs::path& dir)
545{
546 fs::path modelDir;
547
548 // Verify directory path exists and is a directory
549 if (!fs::exists(dir))
550 {
551 // Warning condition. IMG_DIR_BUILTIN might not be used. IMG_DIR_PERSIST
552 // might not exist if an image from IMG_DIR has not been stored.
553 throw RuntimeWarning{
554 std::format("Directory does not exist: {}", dir.c_str())};
555 }
556 if (!fs::is_directory(dir))
557 {
558 throw std::runtime_error{
559 std::format("Path is not a directory: {}", dir.c_str())};
560 }
561
562 // Get the model name of the PSUs that have been found. Note that we
563 // might not have found the PSU information yet on D-Bus.
564 std::string model;
Faisal Awada760053d2024-05-16 13:31:32 -0500565 for (const auto& [key, item] : psuStatusMap)
Lei YU58c26e32019-09-27 17:52:06 +0800566 {
Faisal Awada760053d2024-05-16 13:31:32 -0500567 if (!item.model.empty())
Lei YU58c26e32019-09-27 17:52:06 +0800568 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600569 model = item.model;
Faisal Awada760053d2024-05-16 13:31:32 -0500570 break;
571 }
572 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600573 if (!model.empty())
Faisal Awada760053d2024-05-16 13:31:32 -0500574 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600575 // Verify model subdirectory path exists and is a directory
576 auto subDir = dir / model;
577 if (!fs::exists(subDir))
Faisal Awada760053d2024-05-16 13:31:32 -0500578 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600579 // Warning condition. Subdirectory may not exist in IMG_DIR_PERSIST
580 // if no image has been stored there. May also not exist if
581 // firmware update is not supported for this PSU model.
582 throw RuntimeWarning{
583 std::format("Directory does not exist: {}", subDir.c_str())};
Faisal Awada760053d2024-05-16 13:31:32 -0500584 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600585 if (!fs::is_directory(subDir))
Faisal Awada760053d2024-05-16 13:31:32 -0500586 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600587 throw std::runtime_error{
588 std::format("Path is not a directory: {}", subDir.c_str())};
Lei YU58c26e32019-09-27 17:52:06 +0800589 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600590 modelDir = subDir;
Faisal Awada760053d2024-05-16 13:31:32 -0500591 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600592
593 return modelDir;
Lei YU58c26e32019-09-27 17:52:06 +0800594}
595
Lei YU65207482019-10-11 16:39:36 +0800596std::optional<std::string> ItemUpdater::getLatestVersionId()
597{
Faisal Awadafb86e792024-09-11 10:51:17 -0500598 std::string latestVersion;
599 if (ALWAYS_USE_BUILTIN_IMG_DIR)
600 {
601 latestVersion = getFWVersionFromBuiltinDir();
602 }
603 else
604 {
605 latestVersion = utils::getLatestVersion(versionStrings);
606 }
Lei YU65207482019-10-11 16:39:36 +0800607 if (latestVersion.empty())
608 {
609 return {};
610 }
611
612 std::optional<std::string> versionId;
613 for (const auto& v : versions)
614 {
615 if (v.second->version() == latestVersion)
616 {
617 versionId = v.first;
618 break;
619 }
620 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600621 if (!versionId.has_value())
622 {
623 lg2::error("Unable to find versionId for latest version {VERSION}",
624 "VERSION", latestVersion);
625 }
Lei YU65207482019-10-11 16:39:36 +0800626 return versionId;
627}
628
Lei YU63f9e712019-10-12 15:16:55 +0800629void ItemUpdater::syncToLatestImage()
630{
631 auto latestVersionId = getLatestVersionId();
632 if (!latestVersionId)
633 {
634 return;
635 }
636 const auto& it = activations.find(*latestVersionId);
Shawn McCarney487e2e12024-11-25 17:19:46 -0600637 if (it == activations.end())
638
639 {
640 lg2::error("Unable to find Activation for versionId {VERSION_ID}",
641 "VERSION_ID", *latestVersionId);
642 return;
643 }
Lei YU63f9e712019-10-12 15:16:55 +0800644 const auto& activation = it->second;
645 const auto& assocs = activation->associations();
646
Shawn McCarneyd57bd2f2024-12-02 18:40:28 -0600647 auto paths = utils::getPSUInventoryPaths(bus);
Lei YU63f9e712019-10-12 15:16:55 +0800648 for (const auto& p : paths)
649 {
Shawn McCarney783406e2024-11-17 21:49:37 -0600650 // If there is a present PSU that is not associated with the latest
Faisal Awada760053d2024-05-16 13:31:32 -0500651 // image, run the activation so that all PSUs are running the same
652 // latest image.
Shawn McCarney783406e2024-11-17 21:49:37 -0600653 if (psuStatusMap.contains(p) && psuStatusMap[p].present)
Lei YU63f9e712019-10-12 15:16:55 +0800654 {
Shawn McCarney783406e2024-11-17 21:49:37 -0600655 if (!utils::isAssociated(p, assocs))
656 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600657 lg2::info("Automatically update PSUs to versionId {VERSION_ID}",
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600658 "VERSION_ID", *latestVersionId);
Shawn McCarney783406e2024-11-17 21:49:37 -0600659 invokeActivation(activation);
660 break;
661 }
Lei YU63f9e712019-10-12 15:16:55 +0800662 }
663 }
664}
665
666void ItemUpdater::invokeActivation(
667 const std::unique_ptr<Activation>& activation)
668{
669 activation->requestedActivation(Activation::RequestedActivations::Active);
670}
671
Shawn McCarney487e2e12024-11-25 17:19:46 -0600672void ItemUpdater::onPSUInterfacesAdded(sdbusplus::message_t& msg)
Faisal Awada760053d2024-05-16 13:31:32 -0500673{
Shawn McCarney783406e2024-11-17 21:49:37 -0600674 // Maintain static set of valid PSU paths. This is needed if PSU interface
675 // comes in a separate InterfacesAdded message from Item interface.
676 static std::set<std::string> psuPaths{};
Faisal Awada760053d2024-05-16 13:31:32 -0500677
Shawn McCarney783406e2024-11-17 21:49:37 -0600678 try
Shawn McCarney73a6f0d2024-10-30 16:11:29 -0500679 {
Shawn McCarney783406e2024-11-17 21:49:37 -0600680 sdbusplus::message::object_path objPath;
Shawn McCarney487e2e12024-11-25 17:19:46 -0600681 InterfacesAddedMap interfaces;
Shawn McCarney783406e2024-11-17 21:49:37 -0600682 msg.read(objPath, interfaces);
683 std::string path = objPath.str;
Shawn McCarney73a6f0d2024-10-30 16:11:29 -0500684
Shawn McCarney783406e2024-11-17 21:49:37 -0600685 if (interfaces.contains(PSU_INVENTORY_IFACE))
Faisal Awada760053d2024-05-16 13:31:32 -0500686 {
Shawn McCarney783406e2024-11-17 21:49:37 -0600687 psuPaths.insert(path);
Faisal Awada760053d2024-05-16 13:31:32 -0500688 }
Shawn McCarney783406e2024-11-17 21:49:37 -0600689
690 if (interfaces.contains(ITEM_IFACE) && psuPaths.contains(path) &&
691 !psuStatusMap.contains(path))
Faisal Awada760053d2024-05-16 13:31:32 -0500692 {
Shawn McCarney783406e2024-11-17 21:49:37 -0600693 auto interface = interfaces[ITEM_IFACE];
694 if (interface.contains(PRESENT))
695 {
696 addPsuToStatusMap(path);
697 psuStatusMap[path].present = std::get<bool>(interface[PRESENT]);
698 handlePSUPresenceChanged(path);
699 if (psuStatusMap[path].present)
700 {
701 // Check if there are new PSU images to update
702 processStoredImage();
703 syncToLatestImage();
704 }
705 }
Faisal Awada760053d2024-05-16 13:31:32 -0500706 }
707 }
Shawn McCarney783406e2024-11-17 21:49:37 -0600708 catch (const std::exception& e)
Faisal Awada760053d2024-05-16 13:31:32 -0500709 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600710 lg2::error("Unable to handle inventory InterfacesAdded event: {ERROR}",
711 "ERROR", e);
Faisal Awada760053d2024-05-16 13:31:32 -0500712 }
713}
714
715void ItemUpdater::processPSUImageAndSyncToLatest()
716{
717 processPSUImage();
718 processStoredImage();
719 syncToLatestImage();
720}
721
Faisal Awadafb86e792024-09-11 10:51:17 -0500722std::string ItemUpdater::getFWVersionFromBuiltinDir()
723{
724 std::string version;
725 for (const auto& activation : activations)
726 {
727 if (activation.second->path().starts_with(IMG_DIR_BUILTIN))
728 {
729 std::string versionId = activation.second->getVersionId();
730 auto it = versions.find(versionId);
731 if (it != versions.end())
732 {
733 const auto& versionPtr = it->second;
734 version = versionPtr->version();
735 break;
736 }
737 }
738 }
739 return version;
740}
741
Lei YU01539e72019-07-31 10:57:38 +0800742} // namespace updater
743} // namespace software
744} // namespace phosphor