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