1 #include "config.h" 2 3 #include "item_updater.hpp" 4 5 #include "utils.hpp" 6 7 #include <cassert> 8 #include <filesystem> 9 #include <phosphor-logging/elog-errors.hpp> 10 #include <phosphor-logging/log.hpp> 11 #include <xyz/openbmc_project/Common/error.hpp> 12 13 namespace 14 { 15 constexpr auto MANIFEST_VERSION = "version"; 16 constexpr auto MANIFEST_EXTENDED_VERSION = "extended_version"; 17 } // namespace 18 19 namespace phosphor 20 { 21 namespace software 22 { 23 namespace updater 24 { 25 namespace server = sdbusplus::xyz::openbmc_project::Software::server; 26 27 using namespace sdbusplus::xyz::openbmc_project::Common::Error; 28 using namespace phosphor::logging; 29 using SVersion = server::Version; 30 using VersionPurpose = SVersion::VersionPurpose; 31 32 void ItemUpdater::createActivation(sdbusplus::message::message& m) 33 { 34 namespace msg = sdbusplus::message; 35 namespace variant_ns = msg::variant_ns; 36 37 sdbusplus::message::object_path objPath; 38 std::map<std::string, std::map<std::string, msg::variant<std::string>>> 39 interfaces; 40 m.read(objPath, interfaces); 41 42 std::string path(std::move(objPath)); 43 std::string filePath; 44 auto purpose = VersionPurpose::Unknown; 45 std::string version; 46 47 for (const auto& [interfaceName, propertyMap] : interfaces) 48 { 49 if (interfaceName == VERSION_IFACE) 50 { 51 for (const auto& [propertyName, propertyValue] : propertyMap) 52 { 53 if (propertyName == "Purpose") 54 { 55 // Only process the PSU images 56 auto value = SVersion::convertVersionPurposeFromString( 57 variant_ns::get<std::string>(propertyValue)); 58 59 if (value == VersionPurpose::PSU) 60 { 61 purpose = value; 62 } 63 } 64 else if (propertyName == VERSION) 65 { 66 version = variant_ns::get<std::string>(propertyValue); 67 } 68 } 69 } 70 else if (interfaceName == FILEPATH_IFACE) 71 { 72 const auto& it = propertyMap.find("Path"); 73 if (it != propertyMap.end()) 74 { 75 filePath = variant_ns::get<std::string>(it->second); 76 } 77 } 78 } 79 if ((filePath.empty()) || (purpose == VersionPurpose::Unknown)) 80 { 81 return; 82 } 83 84 // Version id is the last item in the path 85 auto pos = path.rfind("/"); 86 if (pos == std::string::npos) 87 { 88 log<level::ERR>("No version id found in object path", 89 entry("OBJPATH=%s", path.c_str())); 90 return; 91 } 92 93 auto versionId = path.substr(pos + 1); 94 95 if (activations.find(versionId) == activations.end()) 96 { 97 // Determine the Activation state by processing the given image dir. 98 AssociationList associations; 99 auto activationState = Activation::Status::Ready; 100 101 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION, 102 ACTIVATION_REV_ASSOCIATION, 103 PSU_INVENTORY_PATH_BASE)); 104 105 fs::path manifestPath(filePath); 106 manifestPath /= MANIFEST_FILE; 107 std::string extendedVersion = 108 Version::getValue(manifestPath, {MANIFEST_EXTENDED_VERSION}); 109 110 auto activation = 111 createActivationObject(path, versionId, extendedVersion, 112 activationState, associations, filePath); 113 activations.emplace(versionId, std::move(activation)); 114 115 auto versionPtr = 116 createVersionObject(path, versionId, version, purpose); 117 versions.emplace(versionId, std::move(versionPtr)); 118 } 119 return; 120 } 121 122 void ItemUpdater::erase(const std::string& versionId) 123 { 124 auto it = versions.find(versionId); 125 if (it == versions.end()) 126 { 127 log<level::ERR>(("Error: Failed to find version " + versionId + 128 " in item updater versions map." 129 " Unable to remove.") 130 .c_str()); 131 } 132 else 133 { 134 versions.erase(versionId); 135 } 136 137 // Removing entry in activations map 138 auto ita = activations.find(versionId); 139 if (ita == activations.end()) 140 { 141 log<level::ERR>(("Error: Failed to find version " + versionId + 142 " in item updater activations map." 143 " Unable to remove.") 144 .c_str()); 145 } 146 else 147 { 148 activations.erase(versionId); 149 } 150 } 151 152 void ItemUpdater::createActiveAssociation(const std::string& path) 153 { 154 assocs.emplace_back( 155 std::make_tuple(ACTIVE_FWD_ASSOCIATION, ACTIVE_REV_ASSOCIATION, path)); 156 associations(assocs); 157 } 158 159 void ItemUpdater::addFunctionalAssociation(const std::string& path) 160 { 161 assocs.emplace_back(std::make_tuple(FUNCTIONAL_FWD_ASSOCIATION, 162 FUNCTIONAL_REV_ASSOCIATION, path)); 163 associations(assocs); 164 } 165 166 void ItemUpdater::removeAssociation(const std::string& path) 167 { 168 for (auto iter = assocs.begin(); iter != assocs.end();) 169 { 170 if ((std::get<2>(*iter)).compare(path) == 0) 171 { 172 iter = assocs.erase(iter); 173 associations(assocs); 174 } 175 else 176 { 177 ++iter; 178 } 179 } 180 } 181 182 void 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 197 std::unique_ptr<Activation> ItemUpdater::createActivationObject( 198 const std::string& path, const std::string& versionId, 199 const std::string& extVersion, Activation::Status activationStatus, 200 const AssociationList& assocs, const std::string& filePath) 201 { 202 return std::make_unique<Activation>(bus, path, versionId, extVersion, 203 activationStatus, assocs, filePath, 204 this, this); 205 } 206 207 void 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); 222 psuPathActivationMap.emplace(psuInventoryPath, it->second); 223 } 224 else 225 { 226 // Create a new object for running PSU inventory 227 AssociationList associations; 228 auto activationState = Activation::Status::Active; 229 230 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION, 231 ACTIVATION_REV_ASSOCIATION, 232 psuInventoryPath)); 233 234 auto activation = createActivationObject( 235 path, versionId, "", activationState, associations, ""); 236 activations.emplace(versionId, std::move(activation)); 237 psuPathActivationMap.emplace(psuInventoryPath, activations[versionId]); 238 239 auto versionPtr = createVersionObject(path, versionId, psuVersion, 240 VersionPurpose::PSU); 241 versions.emplace(versionId, std::move(versionPtr)); 242 243 createActiveAssociation(path); 244 addFunctionalAssociation(path); 245 } 246 } 247 248 void ItemUpdater::removePsuObject(const std::string& psuInventoryPath) 249 { 250 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 275 erase(activationPtr->getVersionId()); 276 } 277 else 278 { 279 // Update association 280 activationPtr->associations(associations); 281 } 282 } 283 284 std::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 288 versionPurpose) 289 { 290 versionStrings.insert(versionString); 291 auto version = std::make_unique<Version>( 292 bus, objPath, versionId, versionString, versionPurpose, 293 std::bind(&ItemUpdater::erase, this, std::placeholders::_1)); 294 return version; 295 } 296 297 void ItemUpdater::onPsuInventoryChangedMsg(sdbusplus::message::message& msg) 298 { 299 using Interface = std::string; 300 Interface interface; 301 Properties properties; 302 std::string psuPath = msg.get_path(); 303 304 msg.read(interface, properties); 305 onPsuInventoryChanged(psuPath, properties); 306 } 307 308 void ItemUpdater::onPsuInventoryChanged(const std::string& psuPath, 309 const Properties& properties) 310 { 311 bool present; 312 std::string version; 313 314 // Only present property is interested 315 auto p = properties.find(PRESENT); 316 if (p == properties.end()) 317 { 318 return; 319 } 320 present = sdbusplus::message::variant_ns::get<bool>(p->second); 321 322 if (present) 323 { 324 version = utils::getVersion(psuPath); 325 if (!version.empty()) 326 { 327 createPsuObject(psuPath, version); 328 } 329 } 330 else 331 { 332 // Remove object or association 333 removePsuObject(psuPath); 334 } 335 } 336 337 void ItemUpdater::processPSUImage() 338 { 339 auto paths = utils::getPSUInventoryPath(bus); 340 for (const auto& p : paths) 341 { 342 auto service = utils::getService(bus, p.c_str(), ITEM_IFACE); 343 auto present = utils::getProperty<bool>(bus, service.c_str(), p.c_str(), 344 ITEM_IFACE, PRESENT); 345 auto version = utils::getVersion(p); 346 if (present && !version.empty()) 347 { 348 createPsuObject(p, version); 349 } 350 // Add matches for PSU Inventory's property changes 351 psuMatches.emplace_back( 352 bus, MatchRules::propertiesChanged(p, ITEM_IFACE), 353 std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this, 354 std::placeholders::_1)); 355 } 356 } 357 358 void ItemUpdater::processStoredImage() 359 { 360 scanDirectory(IMG_DIR_BUILTIN); 361 scanDirectory(IMG_DIR_PERSIST); 362 } 363 364 void 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 432 std::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 453 void 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 481 void ItemUpdater::invokeActivation( 482 const std::unique_ptr<Activation>& activation) 483 { 484 activation->requestedActivation(Activation::RequestedActivations::Active); 485 } 486 487 } // namespace updater 488 } // namespace software 489 } // namespace phosphor 490