1 #include "config.h" 2 3 #include "item_updater.hpp" 4 5 #include "utils.hpp" 6 7 #include <phosphor-logging/elog-errors.hpp> 8 #include <phosphor-logging/log.hpp> 9 #include <xyz/openbmc_project/Common/error.hpp> 10 11 #include <cassert> 12 #include <filesystem> 13 14 namespace 15 { 16 constexpr auto MANIFEST_VERSION = "version"; 17 constexpr auto MANIFEST_EXTENDED_VERSION = "extended_version"; 18 } // namespace 19 20 namespace phosphor 21 { 22 namespace software 23 { 24 namespace updater 25 { 26 namespace server = sdbusplus::xyz::openbmc_project::Software::server; 27 28 using namespace sdbusplus::xyz::openbmc_project::Common::Error; 29 using namespace phosphor::logging; 30 using SVersion = server::Version; 31 using VersionPurpose = SVersion::VersionPurpose; 32 33 void ItemUpdater::createActivation(sdbusplus::message_t& m) 34 { 35 sdbusplus::message::object_path objPath; 36 std::map<std::string, std::map<std::string, std::variant<std::string>>> 37 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( 55 std::get<std::string>(propertyValue)); 56 57 if (value == VersionPurpose::PSU) 58 { 59 purpose = value; 60 } 61 } 62 else if (propertyName == VERSION) 63 { 64 version = std::get<std::string>(propertyValue); 65 } 66 } 67 } 68 else if (interfaceName == FILEPATH_IFACE) 69 { 70 const auto& it = propertyMap.find("Path"); 71 if (it != propertyMap.end()) 72 { 73 filePath = std::get<std::string>(it->second); 74 } 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. 96 AssociationList associations; 97 auto activationState = Activation::Status::Ready; 98 99 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION, 100 ACTIVATION_REV_ASSOCIATION, 101 PSU_INVENTORY_PATH_BASE)); 102 103 fs::path manifestPath(filePath); 104 manifestPath /= MANIFEST_FILE; 105 std::string extendedVersion = 106 Version::getValue(manifestPath, {MANIFEST_EXTENDED_VERSION}); 107 108 auto activation = 109 createActivationObject(path, versionId, extendedVersion, 110 activationState, associations, filePath); 111 activations.emplace(versionId, std::move(activation)); 112 113 auto versionPtr = createVersionObject(path, versionId, version, 114 purpose); 115 versions.emplace(versionId, std::move(versionPtr)); 116 } 117 return; 118 } 119 120 void ItemUpdater::erase(const std::string& versionId) 121 { 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 { 132 versionStrings.erase(it->second->getVersionString()); 133 versions.erase(it); 134 } 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 151 void 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 158 void ItemUpdater::addFunctionalAssociation(const std::string& path) 159 { 160 assocs.emplace_back(std::make_tuple(FUNCTIONAL_FWD_ASSOCIATION, 161 FUNCTIONAL_REV_ASSOCIATION, path)); 162 associations(assocs); 163 } 164 165 void 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 172 void 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 188 void 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 } 201 202 auto it = activations.find(versionId); 203 assert(it != activations.end()); 204 psuPathActivationMap.emplace(psuInventoryPath, it->second); 205 } 206 207 std::unique_ptr<Activation> ItemUpdater::createActivationObject( 208 const std::string& path, const std::string& versionId, 209 const std::string& extVersion, Activation::Status activationStatus, 210 const AssociationList& assocs, const std::string& filePath) 211 { 212 return std::make_unique<Activation>(bus, path, versionId, extVersion, 213 activationStatus, assocs, filePath, 214 this, this); 215 } 216 217 void 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); 232 psuPathActivationMap.emplace(psuInventoryPath, it->second); 233 } 234 else 235 { 236 // Create a new object for running PSU inventory 237 AssociationList associations; 238 auto activationState = Activation::Status::Active; 239 240 associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION, 241 ACTIVATION_REV_ASSOCIATION, 242 psuInventoryPath)); 243 244 auto activation = createActivationObject( 245 path, versionId, "", activationState, associations, ""); 246 activations.emplace(versionId, std::move(activation)); 247 psuPathActivationMap.emplace(psuInventoryPath, activations[versionId]); 248 249 auto versionPtr = createVersionObject(path, versionId, psuVersion, 250 VersionPurpose::PSU); 251 versions.emplace(versionId, std::move(versionPtr)); 252 253 createActiveAssociation(path); 254 addFunctionalAssociation(path); 255 addUpdateableAssociation(path); 256 } 257 } 258 259 void ItemUpdater::removePsuObject(const std::string& psuInventoryPath) 260 { 261 psuStatusMap[psuInventoryPath] = {false, ""}; 262 263 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 288 erase(activationPtr->getVersionId()); 289 } 290 else 291 { 292 // Update association 293 activationPtr->associations(associations); 294 } 295 } 296 297 std::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 301 versionPurpose) 302 { 303 versionStrings.insert(versionString); 304 auto version = std::make_unique<Version>( 305 bus, objPath, versionId, versionString, versionPurpose, 306 std::bind(&ItemUpdater::erase, this, std::placeholders::_1)); 307 return version; 308 } 309 310 void ItemUpdater::onPsuInventoryChangedMsg(sdbusplus::message_t& msg) 311 { 312 using Interface = std::string; 313 Interface interface; 314 Properties properties; 315 std::string psuPath = msg.get_path(); 316 317 msg.read(interface, properties); 318 onPsuInventoryChanged(psuPath, properties); 319 } 320 321 void ItemUpdater::onPsuInventoryChanged(const std::string& psuPath, 322 const Properties& properties) 323 { 324 std::optional<bool> present; 325 std::optional<std::string> model; 326 327 // 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. 330 auto p = properties.find(PRESENT); 331 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()) 345 { 346 return; 347 } 348 349 if (psuStatusMap[psuPath].present) 350 { 351 // 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); 359 if (!version.empty()) 360 { 361 createPsuObject(psuPath, version); 362 // 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())); 370 } 371 } 372 else 373 { 374 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 } 381 // Remove object or association 382 removePsuObject(psuPath); 383 } 384 } 385 386 void ItemUpdater::processPSUImage() 387 { 388 auto paths = utils::getPSUInventoryPath(bus); 389 for (const auto& p : paths) 390 { 391 auto service = utils::getService(bus, p.c_str(), ITEM_IFACE); 392 auto present = utils::getProperty<bool>(bus, service.c_str(), p.c_str(), 393 ITEM_IFACE, PRESENT); 394 auto version = utils::getVersion(p); 395 if (present && !version.empty()) 396 { 397 createPsuObject(p, version); 398 } 399 // Add matches for PSU Inventory's property changes 400 psuMatches.emplace_back( 401 bus, MatchRules::propertiesChanged(p, ITEM_IFACE), 402 std::bind(&ItemUpdater::onPsuInventoryChangedMsg, this, 403 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 408 } 409 } 410 411 void ItemUpdater::processStoredImage() 412 { 413 scanDirectory(IMG_DIR_BUILTIN); 414 scanDirectory(IMG_DIR_PERSIST); 415 } 416 417 void 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 466 auto versionPtr = createVersionObject(objPath, versionId, 467 version, purpose); 468 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 485 std::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 506 void 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 534 void ItemUpdater::invokeActivation( 535 const std::unique_ptr<Activation>& activation) 536 { 537 activation->requestedActivation(Activation::RequestedActivations::Active); 538 } 539 540 } // namespace updater 541 } // namespace software 542 } // namespace phosphor 543