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_t& m) 33 { 34 sdbusplus::message::object_path objPath; 35 std::map<std::string, std::map<std::string, std::variant<std::string>>> 36 interfaces; 37 m.read(objPath, interfaces); 38 39 std::string path(std::move(objPath)); 40 std::string filePath; 41 auto purpose = VersionPurpose::Unknown; 42 std::string version; 43 44 for (const auto& [interfaceName, propertyMap] : interfaces) 45 { 46 if (interfaceName == VERSION_IFACE) 47 { 48 for (const auto& [propertyName, propertyValue] : propertyMap) 49 { 50 if (propertyName == "Purpose") 51 { 52 // Only process the PSU images 53 auto value = SVersion::convertVersionPurposeFromString( 54 std::get<std::string>(propertyValue)); 55 56 if (value == VersionPurpose::PSU) 57 { 58 purpose = value; 59 } 60 } 61 else if (propertyName == VERSION) 62 { 63 version = std::get<std::string>(propertyValue); 64 } 65 } 66 } 67 else if (interfaceName == FILEPATH_IFACE) 68 { 69 const auto& it = propertyMap.find("Path"); 70 if (it != propertyMap.end()) 71 { 72 filePath = std::get<std::string>(it->second); 73 } 74 } 75 } 76 if ((filePath.empty()) || (purpose == VersionPurpose::Unknown)) 77 { 78 return; 79 } 80 81 // Version id is the last item in the path 82 auto pos = path.rfind("/"); 83 if (pos == std::string::npos) 84 { 85 log<level::ERR>("No version id found in object path", 86 entry("OBJPATH=%s", path.c_str())); 87 return; 88 } 89 90 auto versionId = path.substr(pos + 1); 91 92 if (activations.find(versionId) == activations.end()) 93 { 94 // Determine the Activation state by processing the given image dir. 95 AssociationList associations; 96 auto activationState = Activation::Status::Ready; 97 98 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION, 99 ACTIVATION_REV_ASSOCIATION, 100 PSU_INVENTORY_PATH_BASE)); 101 102 fs::path manifestPath(filePath); 103 manifestPath /= MANIFEST_FILE; 104 std::string extendedVersion = 105 Version::getValue(manifestPath, {MANIFEST_EXTENDED_VERSION}); 106 107 auto activation = 108 createActivationObject(path, versionId, extendedVersion, 109 activationState, associations, filePath); 110 activations.emplace(versionId, std::move(activation)); 111 112 auto versionPtr = 113 createVersionObject(path, versionId, version, purpose); 114 versions.emplace(versionId, std::move(versionPtr)); 115 } 116 return; 117 } 118 119 void ItemUpdater::erase(const std::string& versionId) 120 { 121 auto it = versions.find(versionId); 122 if (it == versions.end()) 123 { 124 log<level::ERR>(("Error: Failed to find version " + versionId + 125 " in item updater versions map." 126 " Unable to remove.") 127 .c_str()); 128 } 129 else 130 { 131 versionStrings.erase(it->second->getVersionString()); 132 versions.erase(it); 133 } 134 135 // Removing entry in activations map 136 auto ita = activations.find(versionId); 137 if (ita == activations.end()) 138 { 139 log<level::ERR>(("Error: Failed to find version " + versionId + 140 " in item updater activations map." 141 " Unable to remove.") 142 .c_str()); 143 } 144 else 145 { 146 activations.erase(versionId); 147 } 148 } 149 150 void ItemUpdater::createActiveAssociation(const std::string& path) 151 { 152 assocs.emplace_back( 153 std::make_tuple(ACTIVE_FWD_ASSOCIATION, ACTIVE_REV_ASSOCIATION, path)); 154 associations(assocs); 155 } 156 157 void ItemUpdater::addFunctionalAssociation(const std::string& path) 158 { 159 assocs.emplace_back(std::make_tuple(FUNCTIONAL_FWD_ASSOCIATION, 160 FUNCTIONAL_REV_ASSOCIATION, path)); 161 associations(assocs); 162 } 163 164 void ItemUpdater::addUpdateableAssociation(const std::string& path) 165 { 166 assocs.emplace_back(std::make_tuple(UPDATEABLE_FWD_ASSOCIATION, 167 UPDATEABLE_REV_ASSOCIATION, path)); 168 associations(assocs); 169 } 170 171 void ItemUpdater::removeAssociation(const std::string& path) 172 { 173 for (auto iter = assocs.begin(); iter != assocs.end();) 174 { 175 if ((std::get<2>(*iter)).compare(path) == 0) 176 { 177 iter = assocs.erase(iter); 178 associations(assocs); 179 } 180 else 181 { 182 ++iter; 183 } 184 } 185 } 186 187 void ItemUpdater::onUpdateDone(const std::string& versionId, 188 const std::string& psuInventoryPath) 189 { 190 // After update is done, remove old activation objects 191 for (auto it = activations.begin(); it != activations.end(); ++it) 192 { 193 if (it->second->getVersionId() != versionId && 194 utils::isAssociated(psuInventoryPath, it->second->associations())) 195 { 196 removePsuObject(psuInventoryPath); 197 break; 198 } 199 } 200 201 auto it = activations.find(versionId); 202 assert(it != activations.end()); 203 psuPathActivationMap.emplace(psuInventoryPath, it->second); 204 } 205 206 std::unique_ptr<Activation> ItemUpdater::createActivationObject( 207 const std::string& path, const std::string& versionId, 208 const std::string& extVersion, Activation::Status activationStatus, 209 const AssociationList& assocs, const std::string& filePath) 210 { 211 return std::make_unique<Activation>(bus, path, versionId, extVersion, 212 activationStatus, assocs, filePath, 213 this, this); 214 } 215 216 void ItemUpdater::createPsuObject(const std::string& psuInventoryPath, 217 const std::string& psuVersion) 218 { 219 auto versionId = utils::getVersionId(psuVersion); 220 auto path = std::string(SOFTWARE_OBJPATH) + "/" + versionId; 221 222 auto it = activations.find(versionId); 223 if (it != activations.end()) 224 { 225 // The versionId is already created, associate the path 226 auto associations = it->second->associations(); 227 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION, 228 ACTIVATION_REV_ASSOCIATION, 229 psuInventoryPath)); 230 it->second->associations(associations); 231 psuPathActivationMap.emplace(psuInventoryPath, it->second); 232 } 233 else 234 { 235 // Create a new object for running PSU inventory 236 AssociationList associations; 237 auto activationState = Activation::Status::Active; 238 239 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION, 240 ACTIVATION_REV_ASSOCIATION, 241 psuInventoryPath)); 242 243 auto activation = createActivationObject( 244 path, versionId, "", activationState, associations, ""); 245 activations.emplace(versionId, std::move(activation)); 246 psuPathActivationMap.emplace(psuInventoryPath, activations[versionId]); 247 248 auto versionPtr = createVersionObject(path, versionId, psuVersion, 249 VersionPurpose::PSU); 250 versions.emplace(versionId, std::move(versionPtr)); 251 252 createActiveAssociation(path); 253 addFunctionalAssociation(path); 254 addUpdateableAssociation(path); 255 } 256 } 257 258 void ItemUpdater::removePsuObject(const std::string& psuInventoryPath) 259 { 260 psuStatusMap[psuInventoryPath] = {false, ""}; 261 262 auto it = psuPathActivationMap.find(psuInventoryPath); 263 if (it == psuPathActivationMap.end()) 264 { 265 log<level::ERR>("No Activation found for PSU", 266 entry("PSUPATH=%s", psuInventoryPath.c_str())); 267 return; 268 } 269 const auto& activationPtr = it->second; 270 psuPathActivationMap.erase(psuInventoryPath); 271 272 auto associations = activationPtr->associations(); 273 for (auto iter = associations.begin(); iter != associations.end();) 274 { 275 if ((std::get<2>(*iter)).compare(psuInventoryPath) == 0) 276 { 277 iter = associations.erase(iter); 278 } 279 else 280 { 281 ++iter; 282 } 283 } 284 if (associations.empty()) 285 { 286 // Remove the activation 287 erase(activationPtr->getVersionId()); 288 } 289 else 290 { 291 // Update association 292 activationPtr->associations(associations); 293 } 294 } 295 296 std::unique_ptr<Version> ItemUpdater::createVersionObject( 297 const std::string& objPath, const std::string& versionId, 298 const std::string& versionString, 299 sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose 300 versionPurpose) 301 { 302 versionStrings.insert(versionString); 303 auto version = std::make_unique<Version>( 304 bus, objPath, versionId, versionString, versionPurpose, 305 std::bind(&ItemUpdater::erase, this, std::placeholders::_1)); 306 return version; 307 } 308 309 void ItemUpdater::onPsuInventoryChangedMsg(sdbusplus::message_t& msg) 310 { 311 using Interface = std::string; 312 Interface interface; 313 Properties properties; 314 std::string psuPath = msg.get_path(); 315 316 msg.read(interface, properties); 317 onPsuInventoryChanged(psuPath, properties); 318 } 319 320 void ItemUpdater::onPsuInventoryChanged(const std::string& psuPath, 321 const Properties& properties) 322 { 323 std::optional<bool> present; 324 std::optional<std::string> model; 325 326 // The code was expecting to get callback on multiple properties changed. 327 // But in practice, the callback is received one-by-one for each property. 328 // So it has to handle Present and Version property separately. 329 auto p = properties.find(PRESENT); 330 if (p != properties.end()) 331 { 332 present = std::get<bool>(p->second); 333 psuStatusMap[psuPath].present = *present; 334 } 335 p = properties.find(MODEL); 336 if (p != properties.end()) 337 { 338 model = std::get<std::string>(p->second); 339 psuStatusMap[psuPath].model = *model; 340 } 341 342 // If present or model is not changed, ignore 343 if (!present.has_value() && !model.has_value()) 344 { 345 return; 346 } 347 348 if (psuStatusMap[psuPath].present) 349 { 350 // If model is not updated, let's wait for it 351 if (psuStatusMap[psuPath].model.empty()) 352 { 353 log<level::DEBUG>("Waiting for model to be updated"); 354 return; 355 } 356 357 auto version = utils::getVersion(psuPath); 358 if (!version.empty()) 359 { 360 createPsuObject(psuPath, version); 361 // Check if there is new PSU images to update 362 syncToLatestImage(); 363 } 364 else 365 { 366 // TODO: log an event 367 log<level::ERR>("Failed to get PSU version", 368 entry("PSU=%s", psuPath.c_str())); 369 } 370 } 371 else 372 { 373 if (!present.has_value()) 374 { 375 // If a PSU is plugged out, model property is update to empty as 376 // well, and we get callback here, but ignore that because it is 377 // handled by "Present" callback. 378 return; 379 } 380 // Remove object or association 381 removePsuObject(psuPath); 382 } 383 } 384 385 void ItemUpdater::processPSUImage() 386 { 387 auto paths = utils::getPSUInventoryPath(bus); 388 for (const auto& p : paths) 389 { 390 auto service = utils::getService(bus, p.c_str(), ITEM_IFACE); 391 auto present = utils::getProperty<bool>(bus, service.c_str(), p.c_str(), 392 ITEM_IFACE, PRESENT); 393 auto version = utils::getVersion(p); 394 if (present && !version.empty()) 395 { 396 createPsuObject(p, version); 397 } 398 // Add matches for PSU Inventory's property changes 399 psuMatches.emplace_back( 400 bus, MatchRules::propertiesChanged(p, ITEM_IFACE), 401 std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this, 402 std::placeholders::_1)); // For present 403 psuMatches.emplace_back( 404 bus, MatchRules::propertiesChanged(p, ASSET_IFACE), 405 std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this, 406 std::placeholders::_1)); // For model 407 } 408 } 409 410 void ItemUpdater::processStoredImage() 411 { 412 scanDirectory(IMG_DIR_BUILTIN); 413 scanDirectory(IMG_DIR_PERSIST); 414 } 415 416 void ItemUpdater::scanDirectory(const fs::path& dir) 417 { 418 // The directory shall put PSU images in directories named with model 419 if (!fs::exists(dir)) 420 { 421 // Skip 422 return; 423 } 424 if (!fs::is_directory(dir)) 425 { 426 log<level::ERR>("The path is not a directory", 427 entry("PATH=%s", dir.c_str())); 428 return; 429 } 430 for (const auto& d : fs::directory_iterator(dir)) 431 { 432 // If the model in manifest does not match the dir name 433 // Log a warning and skip it 434 auto path = d.path(); 435 auto manifest = path / MANIFEST_FILE; 436 if (fs::exists(manifest)) 437 { 438 auto ret = Version::getValues( 439 manifest.string(), 440 {MANIFEST_VERSION, MANIFEST_EXTENDED_VERSION}); 441 auto version = ret[MANIFEST_VERSION]; 442 auto extVersion = ret[MANIFEST_EXTENDED_VERSION]; 443 auto info = Version::getExtVersionInfo(extVersion); 444 auto model = info["model"]; 445 if (path.stem() != model) 446 { 447 log<level::ERR>("Unmatched model", 448 entry("PATH=%s", path.c_str()), 449 entry("MODEL=%s", model.c_str())); 450 continue; 451 } 452 auto versionId = utils::getVersionId(version); 453 auto it = activations.find(versionId); 454 if (it == activations.end()) 455 { 456 // This is a version that is different than the running PSUs 457 auto activationState = Activation::Status::Ready; 458 auto purpose = VersionPurpose::PSU; 459 auto objPath = std::string(SOFTWARE_OBJPATH) + "/" + versionId; 460 461 auto activation = createActivationObject( 462 objPath, versionId, extVersion, activationState, {}, path); 463 activations.emplace(versionId, std::move(activation)); 464 465 auto versionPtr = 466 createVersionObject(objPath, versionId, version, purpose); 467 versions.emplace(versionId, std::move(versionPtr)); 468 } 469 else 470 { 471 // This is a version that a running PSU is using, set the path 472 // on the version object 473 it->second->path(path); 474 } 475 } 476 else 477 { 478 log<level::ERR>("No MANIFEST found", 479 entry("PATH=%s", path.c_str())); 480 } 481 } 482 } 483 484 std::optional<std::string> ItemUpdater::getLatestVersionId() 485 { 486 auto latestVersion = utils::getLatestVersion(versionStrings); 487 if (latestVersion.empty()) 488 { 489 return {}; 490 } 491 492 std::optional<std::string> versionId; 493 for (const auto& v : versions) 494 { 495 if (v.second->version() == latestVersion) 496 { 497 versionId = v.first; 498 break; 499 } 500 } 501 assert(versionId.has_value()); 502 return versionId; 503 } 504 505 void ItemUpdater::syncToLatestImage() 506 { 507 auto latestVersionId = getLatestVersionId(); 508 if (!latestVersionId) 509 { 510 return; 511 } 512 const auto& it = activations.find(*latestVersionId); 513 assert(it != activations.end()); 514 const auto& activation = it->second; 515 const auto& assocs = activation->associations(); 516 517 auto paths = utils::getPSUInventoryPath(bus); 518 for (const auto& p : paths) 519 { 520 // As long as there is a PSU is not associated with the latest image, 521 // run the activation so that all PSUs are running the same latest 522 // image. 523 if (!utils::isAssociated(p, assocs)) 524 { 525 log<level::INFO>("Automatically update PSU", 526 entry("VERSION_ID=%s", latestVersionId->c_str())); 527 invokeActivation(activation); 528 break; 529 } 530 } 531 } 532 533 void ItemUpdater::invokeActivation( 534 const std::unique_ptr<Activation>& activation) 535 { 536 activation->requestedActivation(Activation::RequestedActivations::Active); 537 } 538 539 } // namespace updater 540 } // namespace software 541 } // namespace phosphor 542