blob: a77d2e4b90f8d7a19be24d99999b8af64582a333 [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 {
Shawn McCarney17c2c942024-12-10 17:32:30 -0600538 // Activation already exists. It may have been created for code that is
539 // running on one or more PSUs. Set Path and ExtendedVersion properties.
540 // The properties are not set when the Activation is created for code
541 // running on a PSU. The properties are needed to update other PSUs.
Shawn McCarney487e2e12024-11-25 17:19:46 -0600542 it->second->path(modelDir);
Shawn McCarney17c2c942024-12-10 17:32:30 -0600543 it->second->extendedVersion(extVersion);
Shawn McCarney487e2e12024-11-25 17:19:46 -0600544 }
545}
546
547fs::path ItemUpdater::findModelDirectory(const fs::path& dir)
548{
549 fs::path modelDir;
550
551 // Verify directory path exists and is a directory
552 if (!fs::exists(dir))
553 {
554 // Warning condition. IMG_DIR_BUILTIN might not be used. IMG_DIR_PERSIST
555 // might not exist if an image from IMG_DIR has not been stored.
556 throw RuntimeWarning{
557 std::format("Directory does not exist: {}", dir.c_str())};
558 }
559 if (!fs::is_directory(dir))
560 {
561 throw std::runtime_error{
562 std::format("Path is not a directory: {}", dir.c_str())};
563 }
564
565 // Get the model name of the PSUs that have been found. Note that we
566 // might not have found the PSU information yet on D-Bus.
567 std::string model;
Faisal Awada760053d2024-05-16 13:31:32 -0500568 for (const auto& [key, item] : psuStatusMap)
Lei YU58c26e32019-09-27 17:52:06 +0800569 {
Faisal Awada760053d2024-05-16 13:31:32 -0500570 if (!item.model.empty())
Lei YU58c26e32019-09-27 17:52:06 +0800571 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600572 model = item.model;
Faisal Awada760053d2024-05-16 13:31:32 -0500573 break;
574 }
575 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600576 if (!model.empty())
Faisal Awada760053d2024-05-16 13:31:32 -0500577 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600578 // Verify model subdirectory path exists and is a directory
579 auto subDir = dir / model;
580 if (!fs::exists(subDir))
Faisal Awada760053d2024-05-16 13:31:32 -0500581 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600582 // Warning condition. Subdirectory may not exist in IMG_DIR_PERSIST
583 // if no image has been stored there. May also not exist if
584 // firmware update is not supported for this PSU model.
585 throw RuntimeWarning{
586 std::format("Directory does not exist: {}", subDir.c_str())};
Faisal Awada760053d2024-05-16 13:31:32 -0500587 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600588 if (!fs::is_directory(subDir))
Faisal Awada760053d2024-05-16 13:31:32 -0500589 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600590 throw std::runtime_error{
591 std::format("Path is not a directory: {}", subDir.c_str())};
Lei YU58c26e32019-09-27 17:52:06 +0800592 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600593 modelDir = subDir;
Faisal Awada760053d2024-05-16 13:31:32 -0500594 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600595
596 return modelDir;
Lei YU58c26e32019-09-27 17:52:06 +0800597}
598
Lei YU65207482019-10-11 16:39:36 +0800599std::optional<std::string> ItemUpdater::getLatestVersionId()
600{
Faisal Awadafb86e792024-09-11 10:51:17 -0500601 std::string latestVersion;
602 if (ALWAYS_USE_BUILTIN_IMG_DIR)
603 {
604 latestVersion = getFWVersionFromBuiltinDir();
605 }
606 else
607 {
608 latestVersion = utils::getLatestVersion(versionStrings);
609 }
Lei YU65207482019-10-11 16:39:36 +0800610 if (latestVersion.empty())
611 {
612 return {};
613 }
614
615 std::optional<std::string> versionId;
616 for (const auto& v : versions)
617 {
618 if (v.second->version() == latestVersion)
619 {
620 versionId = v.first;
621 break;
622 }
623 }
Shawn McCarney487e2e12024-11-25 17:19:46 -0600624 if (!versionId.has_value())
625 {
626 lg2::error("Unable to find versionId for latest version {VERSION}",
627 "VERSION", latestVersion);
628 }
Lei YU65207482019-10-11 16:39:36 +0800629 return versionId;
630}
631
Lei YU63f9e712019-10-12 15:16:55 +0800632void ItemUpdater::syncToLatestImage()
633{
634 auto latestVersionId = getLatestVersionId();
635 if (!latestVersionId)
636 {
637 return;
638 }
639 const auto& it = activations.find(*latestVersionId);
Shawn McCarney487e2e12024-11-25 17:19:46 -0600640 if (it == activations.end())
641
642 {
643 lg2::error("Unable to find Activation for versionId {VERSION_ID}",
644 "VERSION_ID", *latestVersionId);
645 return;
646 }
Lei YU63f9e712019-10-12 15:16:55 +0800647 const auto& activation = it->second;
648 const auto& assocs = activation->associations();
649
Shawn McCarneyd57bd2f2024-12-02 18:40:28 -0600650 auto paths = utils::getPSUInventoryPaths(bus);
Lei YU63f9e712019-10-12 15:16:55 +0800651 for (const auto& p : paths)
652 {
Shawn McCarney783406e2024-11-17 21:49:37 -0600653 // If there is a present PSU that is not associated with the latest
Faisal Awada760053d2024-05-16 13:31:32 -0500654 // image, run the activation so that all PSUs are running the same
655 // latest image.
Shawn McCarney783406e2024-11-17 21:49:37 -0600656 if (psuStatusMap.contains(p) && psuStatusMap[p].present)
Lei YU63f9e712019-10-12 15:16:55 +0800657 {
Shawn McCarney783406e2024-11-17 21:49:37 -0600658 if (!utils::isAssociated(p, assocs))
659 {
Shawn McCarney487e2e12024-11-25 17:19:46 -0600660 lg2::info("Automatically update PSUs to versionId {VERSION_ID}",
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600661 "VERSION_ID", *latestVersionId);
Shawn McCarney783406e2024-11-17 21:49:37 -0600662 invokeActivation(activation);
663 break;
664 }
Lei YU63f9e712019-10-12 15:16:55 +0800665 }
666 }
667}
668
669void ItemUpdater::invokeActivation(
670 const std::unique_ptr<Activation>& activation)
671{
672 activation->requestedActivation(Activation::RequestedActivations::Active);
673}
674
Shawn McCarney487e2e12024-11-25 17:19:46 -0600675void ItemUpdater::onPSUInterfacesAdded(sdbusplus::message_t& msg)
Faisal Awada760053d2024-05-16 13:31:32 -0500676{
Shawn McCarney783406e2024-11-17 21:49:37 -0600677 // Maintain static set of valid PSU paths. This is needed if PSU interface
678 // comes in a separate InterfacesAdded message from Item interface.
679 static std::set<std::string> psuPaths{};
Faisal Awada760053d2024-05-16 13:31:32 -0500680
Shawn McCarney783406e2024-11-17 21:49:37 -0600681 try
Shawn McCarney73a6f0d2024-10-30 16:11:29 -0500682 {
Shawn McCarney783406e2024-11-17 21:49:37 -0600683 sdbusplus::message::object_path objPath;
Shawn McCarney487e2e12024-11-25 17:19:46 -0600684 InterfacesAddedMap interfaces;
Shawn McCarney783406e2024-11-17 21:49:37 -0600685 msg.read(objPath, interfaces);
686 std::string path = objPath.str;
Shawn McCarney73a6f0d2024-10-30 16:11:29 -0500687
Shawn McCarney783406e2024-11-17 21:49:37 -0600688 if (interfaces.contains(PSU_INVENTORY_IFACE))
Faisal Awada760053d2024-05-16 13:31:32 -0500689 {
Shawn McCarney783406e2024-11-17 21:49:37 -0600690 psuPaths.insert(path);
Faisal Awada760053d2024-05-16 13:31:32 -0500691 }
Shawn McCarney783406e2024-11-17 21:49:37 -0600692
693 if (interfaces.contains(ITEM_IFACE) && psuPaths.contains(path) &&
694 !psuStatusMap.contains(path))
Faisal Awada760053d2024-05-16 13:31:32 -0500695 {
Shawn McCarney783406e2024-11-17 21:49:37 -0600696 auto interface = interfaces[ITEM_IFACE];
697 if (interface.contains(PRESENT))
698 {
699 addPsuToStatusMap(path);
700 psuStatusMap[path].present = std::get<bool>(interface[PRESENT]);
701 handlePSUPresenceChanged(path);
702 if (psuStatusMap[path].present)
703 {
704 // Check if there are new PSU images to update
705 processStoredImage();
706 syncToLatestImage();
707 }
708 }
Faisal Awada760053d2024-05-16 13:31:32 -0500709 }
710 }
Shawn McCarney783406e2024-11-17 21:49:37 -0600711 catch (const std::exception& e)
Faisal Awada760053d2024-05-16 13:31:32 -0500712 {
Shawn McCarneycdf86de2024-11-26 10:02:14 -0600713 lg2::error("Unable to handle inventory InterfacesAdded event: {ERROR}",
714 "ERROR", e);
Faisal Awada760053d2024-05-16 13:31:32 -0500715 }
716}
717
718void ItemUpdater::processPSUImageAndSyncToLatest()
719{
720 processPSUImage();
721 processStoredImage();
722 syncToLatestImage();
723}
724
Faisal Awadafb86e792024-09-11 10:51:17 -0500725std::string ItemUpdater::getFWVersionFromBuiltinDir()
726{
727 std::string version;
728 for (const auto& activation : activations)
729 {
730 if (activation.second->path().starts_with(IMG_DIR_BUILTIN))
731 {
732 std::string versionId = activation.second->getVersionId();
733 auto it = versions.find(versionId);
734 if (it != versions.end())
735 {
736 const auto& versionPtr = it->second;
737 version = versionPtr->version();
738 break;
739 }
740 }
741 }
742 return version;
743}
744
Lei YU01539e72019-07-31 10:57:38 +0800745} // namespace updater
746} // namespace software
747} // namespace phosphor