|  | #include "config.h" | 
|  |  | 
|  | #include "item_updater.hpp" | 
|  |  | 
|  | #include "images.hpp" | 
|  | #include "serialize.hpp" | 
|  | #include "version.hpp" | 
|  | #include "xyz/openbmc_project/Software/ExtendedVersion/server.hpp" | 
|  | #include "xyz/openbmc_project/Software/Version/server.hpp" | 
|  |  | 
|  | #include <phosphor-logging/elog-errors.hpp> | 
|  | #include <phosphor-logging/elog.hpp> | 
|  | #include <phosphor-logging/lg2.hpp> | 
|  | #include <xyz/openbmc_project/Common/error.hpp> | 
|  | #include <xyz/openbmc_project/Software/Image/error.hpp> | 
|  |  | 
|  | #include <filesystem> | 
|  | #include <fstream> | 
|  | #include <queue> | 
|  | #include <set> | 
|  | #include <string> | 
|  | #include <system_error> | 
|  |  | 
|  | namespace phosphor | 
|  | { | 
|  | namespace software | 
|  | { | 
|  | namespace updater | 
|  | { | 
|  |  | 
|  | // When you see server:: you know we're referencing our base class | 
|  | namespace server = sdbusplus::server::xyz::openbmc_project::software; | 
|  | namespace control = sdbusplus::server::xyz::openbmc_project::control; | 
|  |  | 
|  | PHOSPHOR_LOG2_USING; | 
|  | using namespace phosphor::logging; | 
|  | using namespace sdbusplus::error::xyz::openbmc_project::software::image; | 
|  | using namespace phosphor::software::image; | 
|  | namespace fs = std::filesystem; | 
|  | using NotAllowed = sdbusplus::error::xyz::openbmc_project::common::NotAllowed; | 
|  |  | 
|  | void ItemUpdater::createActivation(sdbusplus::message_t& msg) | 
|  | { | 
|  | using SVersion = server::Version; | 
|  | using VersionPurpose = SVersion::VersionPurpose; | 
|  |  | 
|  | sdbusplus::message::object_path objPath; | 
|  | auto purpose = VersionPurpose::Unknown; | 
|  | std::string extendedVersion; | 
|  | std::string version; | 
|  | std::map<std::string, | 
|  | std::map<std::string, | 
|  | std::variant<std::string, std::vector<std::string>>>> | 
|  | interfaces; | 
|  | msg.read(objPath, interfaces); | 
|  | std::string path(std::move(objPath)); | 
|  | std::string filePath; | 
|  | std::vector<std::string> compatibleNames; | 
|  |  | 
|  | for (const auto& intf : interfaces) | 
|  | { | 
|  | if (intf.first == VERSION_IFACE) | 
|  | { | 
|  | for (const auto& property : intf.second) | 
|  | { | 
|  | if (property.first == "Purpose") | 
|  | { | 
|  | auto value = SVersion::convertVersionPurposeFromString( | 
|  | std::get<std::string>(property.second)); | 
|  | if (value == VersionPurpose::BMC || | 
|  | #ifdef HOST_BIOS_UPGRADE | 
|  | value == VersionPurpose::Host || | 
|  | #endif | 
|  | value == VersionPurpose::System) | 
|  | { | 
|  | purpose = value; | 
|  | } | 
|  | } | 
|  | else if (property.first == "Version") | 
|  | { | 
|  | version = std::get<std::string>(property.second); | 
|  | } | 
|  | } | 
|  | } | 
|  | else if (intf.first == FILEPATH_IFACE) | 
|  | { | 
|  | for (const auto& property : intf.second) | 
|  | { | 
|  | if (property.first == "Path") | 
|  | { | 
|  | filePath = std::get<std::string>(property.second); | 
|  | } | 
|  | } | 
|  | } | 
|  | else if (intf.first == EXTENDED_VERSION_IFACE) | 
|  | { | 
|  | for (const auto& property : intf.second) | 
|  | { | 
|  | if (property.first == "ExtendedVersion") | 
|  | { | 
|  | extendedVersion = std::get<std::string>(property.second); | 
|  | } | 
|  | } | 
|  | } | 
|  | else if (intf.first == COMPATIBLE_IFACE) | 
|  | { | 
|  | for (const auto& property : intf.second) | 
|  | { | 
|  | if (property.first == "Names") | 
|  | { | 
|  | compatibleNames = | 
|  | std::get<std::vector<std::string>>(property.second); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | if (version.empty() || filePath.empty() || | 
|  | purpose == VersionPurpose::Unknown) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Version id is the last item in the path | 
|  | auto pos = path.rfind('/'); | 
|  | if (pos == std::string::npos) | 
|  | { | 
|  | error("No version id found in object path: {PATH}", "PATH", path); | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto versionId = path.substr(pos + 1); | 
|  |  | 
|  | if (activations.find(versionId) == activations.end()) | 
|  | { | 
|  | verifyAndCreateObjects(versionId, path, version, purpose, | 
|  | extendedVersion, filePath, compatibleNames); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | void ItemUpdater::createActivationWithApplyTime( | 
|  | std::string& id, std::string& path, | 
|  | ApplyTimeIntf::RequestedApplyTimes applyTime) | 
|  | { | 
|  | info("Creating Activation object for id: {ID}", "ID", id); | 
|  | AssociationList associations = {}; | 
|  | // Create an association to the BMC inventory item | 
|  | associations.emplace_back( | 
|  | std::make_tuple(ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION, | 
|  | bmcInventoryPath)); | 
|  | activations.insert(std::make_pair( | 
|  | id, std::make_unique<Activation>( | 
|  | ctx, path, *this, id, server::Activation::Activations::NotReady, | 
|  | associations))); | 
|  | activations[id]->applyTime = applyTime; | 
|  | } | 
|  |  | 
|  | ActivationIntf::Activations ItemUpdater::verifyAndCreateObjects( | 
|  | std::string& id, std::string& path, std::string& version, | 
|  | VersionClass::VersionPurpose purpose, std::string& extendedVersion, | 
|  | std ::string& filePath, std::vector<std::string>& compatibleNames) | 
|  | { | 
|  | // Determine the Activation state by processing the given image dir. | 
|  | auto activationState = server::Activation::Activations::Invalid; | 
|  | ItemUpdater::ActivationStatus result; | 
|  | if (purpose == VersionPurpose::BMC || purpose == VersionPurpose::System) | 
|  | { | 
|  | result = ItemUpdater::validateSquashFSImage(filePath); | 
|  | } | 
|  | else | 
|  | { | 
|  | result = ItemUpdater::ActivationStatus::ready; | 
|  | } | 
|  |  | 
|  | AssociationList associations = {}; | 
|  |  | 
|  | if (result == ItemUpdater::ActivationStatus::ready) | 
|  | { | 
|  | activationState = server::Activation::Activations::Ready; | 
|  | // Create an association to the BMC inventory item | 
|  | associations.emplace_back( | 
|  | std::make_tuple(ACTIVATION_FWD_ASSOCIATION, | 
|  | ACTIVATION_REV_ASSOCIATION, bmcInventoryPath)); | 
|  | } | 
|  |  | 
|  | auto versionPtr = std::make_unique<VersionClass>( | 
|  | bus, path, version, purpose, extendedVersion, filePath, compatibleNames, | 
|  | std::bind(&ItemUpdater::erase, this, std::placeholders::_1), id); | 
|  | versionPtr->deleteObject = | 
|  | std::make_unique<phosphor::software::manager::Delete>( | 
|  | bus, path, *versionPtr); | 
|  | versions.insert(std::make_pair(id, std::move(versionPtr))); | 
|  |  | 
|  | auto activation = activations.find(id); | 
|  | if (activation == activations.end()) | 
|  | { | 
|  | activations.insert(std::make_pair( | 
|  | id, std::make_unique<Activation>(ctx, path, *this, id, | 
|  | activationState, associations))); | 
|  | } | 
|  | else | 
|  | { | 
|  | activation->second->activation(activationState); | 
|  | } | 
|  | return activationState; | 
|  | } | 
|  |  | 
|  | bool ItemUpdater::requestActivation(std::string& id) | 
|  | { | 
|  | auto activation = activations.find(id); | 
|  | if (activation == activations.end()) | 
|  | { | 
|  | error("Activation object not found for id: {ID}", "ID", id); | 
|  | return false; | 
|  | } | 
|  | activation->second->requestedActivation( | 
|  | server::Activation::RequestedActivations::Active); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ItemUpdater::updateActivationStatus(std::string& id, | 
|  | ActivationIntf::Activations status) | 
|  | { | 
|  | auto activation = activations.find(id); | 
|  | if (activation == activations.end()) | 
|  | { | 
|  | error("Activation object not found for id: {ID}", "ID", id); | 
|  | return false; | 
|  | } | 
|  | activation->second->activation(status); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void ItemUpdater::createUpdateObject(const std::string& id, | 
|  | const std::string& path) | 
|  | { | 
|  | if (updateManagers.find(id) != updateManagers.end()) | 
|  | { | 
|  | error("UpdateManager object already exists for id: {ID}", "ID", id); | 
|  | return; | 
|  | } | 
|  | updateManagers.insert( | 
|  | std::make_pair(id, std::make_unique<UpdateManager>(ctx, path, *this))); | 
|  | } | 
|  |  | 
|  | void ItemUpdater::processBMCImage() | 
|  | { | 
|  | using VersionClass = phosphor::software::manager::Version; | 
|  |  | 
|  | // Check MEDIA_DIR and create if it does not exist | 
|  | try | 
|  | { | 
|  | if (!fs::is_directory(MEDIA_DIR)) | 
|  | { | 
|  | fs::create_directory(MEDIA_DIR); | 
|  | } | 
|  | } | 
|  | catch (const fs::filesystem_error& e) | 
|  | { | 
|  | error("Failed to prepare dir: {ERROR}", "ERROR", e); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Functional images are mounted as rofs-<location>-functional | 
|  | constexpr auto functionalSuffix = "-functional"; | 
|  | bool functionalFound = false; | 
|  |  | 
|  | // Read os-release from folders under /media/ to get | 
|  | // BMC Software Versions. | 
|  | std::error_code ec; | 
|  | for (const auto& iter : fs::directory_iterator(MEDIA_DIR, ec)) | 
|  | { | 
|  | auto activationState = server::Activation::Activations::Active; | 
|  | static const auto BMC_RO_PREFIX_LEN = strlen(BMC_ROFS_PREFIX); | 
|  |  | 
|  | // Check if the BMC_RO_PREFIXis the prefix of the iter.path | 
|  | if (0 == | 
|  | iter.path().native().compare(0, BMC_RO_PREFIX_LEN, BMC_ROFS_PREFIX)) | 
|  | { | 
|  | // Get the version to calculate the id | 
|  | fs::path releaseFile(OS_RELEASE_FILE); | 
|  | auto osRelease = iter.path() / releaseFile.relative_path(); | 
|  | if (!fs::is_regular_file(osRelease, ec)) | 
|  | { | 
|  | #ifdef BMC_STATIC_DUAL_IMAGE | 
|  | // For dual image, it is possible that the secondary image is | 
|  | // empty or contains invalid data, ignore such case. | 
|  | info("Unable to find osRelease: {PATH}: {ERROR_MSG}", "PATH", | 
|  | osRelease, "ERROR_MSG", ec.message()); | 
|  | #else | 
|  | error("Failed to read osRelease: {PATH}: {ERROR_MSG}", "PATH", | 
|  | osRelease, "ERROR_MSG", ec.message()); | 
|  |  | 
|  | // Try to get the version id from the mount directory name and | 
|  | // call to delete it as this version may be corrupted. Dynamic | 
|  | // volumes created by the UBI layout for example have the id in | 
|  | // the mount directory name. The worst that can happen is that | 
|  | // erase() is called with an non-existent id and returns. | 
|  | auto id = iter.path().native().substr(BMC_RO_PREFIX_LEN); | 
|  | ItemUpdater::erase(id); | 
|  | #endif | 
|  |  | 
|  | continue; | 
|  | } | 
|  | auto version = VersionClass::getBMCVersion(osRelease); | 
|  | if (version.empty()) | 
|  | { | 
|  | error("Failed to read version from osRelease: {PATH}", "PATH", | 
|  | osRelease); | 
|  |  | 
|  | // Try to delete the version, same as above if the | 
|  | // OS_RELEASE_FILE does not exist. | 
|  | auto id = iter.path().native().substr(BMC_RO_PREFIX_LEN); | 
|  | ItemUpdater::erase(id); | 
|  |  | 
|  | continue; | 
|  | } | 
|  |  | 
|  | // The flash location is part of the mount name: rofs-<location> | 
|  | auto flashId = iter.path().native().substr(BMC_RO_PREFIX_LEN); | 
|  |  | 
|  | auto id = VersionClass::getId(version + flashId); | 
|  |  | 
|  | // Check if the id has already been added. This can happen if the | 
|  | // BMC partitions / devices were manually flashed with the same | 
|  | // image. | 
|  | if (versions.find(id) != versions.end()) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | auto functional = false; | 
|  | if (iter.path().native().find(functionalSuffix) != | 
|  | std::string::npos) | 
|  | { | 
|  | // Set functional to true and remove the functional suffix | 
|  | functional = true; | 
|  | flashId.erase(flashId.length() - strlen(functionalSuffix)); | 
|  | functionalFound = true; | 
|  | } | 
|  |  | 
|  | auto purpose = server::Version::VersionPurpose::BMC; | 
|  | restorePurpose(flashId, purpose); | 
|  |  | 
|  | // Read os-release from /etc/ to get the BMC extended version | 
|  | std::string extendedVersion = | 
|  | VersionClass::getBMCExtendedVersion(osRelease); | 
|  |  | 
|  | auto path = fs::path(SOFTWARE_OBJPATH) / id; | 
|  |  | 
|  | // Create functional association and minimum ship level instance if | 
|  | // this is the functional version | 
|  | if (functional) | 
|  | { | 
|  | createFunctionalAssociation(path); | 
|  |  | 
|  | if (minimum_ship_level::enabled()) | 
|  | { | 
|  | minimumVersionObject = | 
|  | std::make_unique<MinimumVersion>(bus, path); | 
|  | minimumVersionObject->minimumVersion( | 
|  | minimum_ship_level::getMinimumVersion()); | 
|  | } | 
|  | } | 
|  |  | 
|  | AssociationList associations; | 
|  |  | 
|  | if (activationState == server::Activation::Activations::Active) | 
|  | { | 
|  | // Create an association to the BMC inventory item | 
|  | associations.emplace_back(std::make_tuple( | 
|  | ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION, | 
|  | bmcInventoryPath)); | 
|  |  | 
|  | // Create an active association since this image is active | 
|  | createActiveAssociation(path); | 
|  | } | 
|  |  | 
|  | // Create Version instance for this version. | 
|  | auto versionPtr = std::make_unique<VersionClass>( | 
|  | bus, path, version, purpose, extendedVersion, flashId, | 
|  | std::vector<std::string>(), | 
|  | std::bind(&ItemUpdater::erase, this, std::placeholders::_1), | 
|  | id); | 
|  | if (functional) | 
|  | { | 
|  | versionPtr->setFunctional(true); | 
|  | } | 
|  | else | 
|  | { | 
|  | versionPtr->deleteObject = | 
|  | std::make_unique<phosphor::software::manager::Delete>( | 
|  | bus, path, *versionPtr); | 
|  | } | 
|  | versions.insert(std::make_pair(id, std::move(versionPtr))); | 
|  |  | 
|  | // Create Activation instance for this version. | 
|  | activations.insert(std::make_pair( | 
|  | id, std::make_unique<Activation>( | 
|  | ctx, path, *this, id, activationState, associations))); | 
|  |  | 
|  | #ifdef BMC_STATIC_DUAL_IMAGE | 
|  | uint8_t priority; | 
|  | if ((functional && (runningImageSlot == 0)) || | 
|  | (!functional && (runningImageSlot == 1))) | 
|  | { | 
|  | priority = 0; | 
|  | } | 
|  | else | 
|  | { | 
|  | priority = 1; | 
|  | } | 
|  | activations.find(id)->second->redundancyPriority = | 
|  | std::make_unique<RedundancyPriority>( | 
|  | bus, path, *(activations.find(id)->second), priority, | 
|  | false); | 
|  | #else | 
|  | // If Active, create RedundancyPriority instance for this | 
|  | // version. | 
|  | if (activationState == server::Activation::Activations::Active) | 
|  | { | 
|  | uint8_t priority = std::numeric_limits<uint8_t>::max(); | 
|  | if (!restorePriority(flashId, priority)) | 
|  | { | 
|  | if (functional) | 
|  | { | 
|  | priority = 0; | 
|  | } | 
|  | else | 
|  | { | 
|  | error( | 
|  | "Unable to restore priority from file for {VERSIONID}", | 
|  | "VERSIONID", id); | 
|  | } | 
|  | } | 
|  | activations.find(id)->second->redundancyPriority = | 
|  | std::make_unique<RedundancyPriority>( | 
|  | bus, path, *(activations.find(id)->second), priority, | 
|  | false); | 
|  | } | 
|  | #endif | 
|  | } | 
|  | } | 
|  |  | 
|  | for (const auto& version : versions) | 
|  | { | 
|  | if ((versions.size() == 1) || (!version.second->isFunctional())) | 
|  | { | 
|  | // This is the only BMC version or the non-functional BMC version | 
|  | // (in a system with more than one flash), hence create Update | 
|  | // object and Updateable association for this version | 
|  | if (useUpdateDBusInterface) | 
|  | { | 
|  | createUpdateObject(version.first, version.second->objPath); | 
|  | } | 
|  | createUpdateableAssociation(version.second->objPath); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!functionalFound) | 
|  | { | 
|  | // If there is no functional version found, read the /etc/os-release and | 
|  | // create rofs-<versionId>-functional under MEDIA_DIR, then call again | 
|  | // processBMCImage() to create the D-Bus interface for it. | 
|  | auto version = VersionClass::getBMCVersion(OS_RELEASE_FILE); | 
|  | auto id = phosphor::software::manager::Version::getId( | 
|  | version + functionalSuffix); | 
|  | auto versionFileDir = BMC_ROFS_PREFIX + id + functionalSuffix + "/etc/"; | 
|  | try | 
|  | { | 
|  | if (!fs::is_directory(versionFileDir)) | 
|  | { | 
|  | fs::create_directories(versionFileDir); | 
|  | } | 
|  | auto versionFilePath = | 
|  | BMC_ROFS_PREFIX + id + functionalSuffix + OS_RELEASE_FILE; | 
|  | fs::create_directory_symlink(OS_RELEASE_FILE, versionFilePath); | 
|  | ItemUpdater::processBMCImage(); | 
|  | } | 
|  | catch (const std::exception& e) | 
|  | { | 
|  | error("Exception during processing: {ERROR}", "ERROR", e); | 
|  | } | 
|  | } | 
|  |  | 
|  | mirrorUbootToAlt(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | void ItemUpdater::erase(std::string entryId) | 
|  | { | 
|  | // Find entry in versions map | 
|  | auto it = versions.find(entryId); | 
|  | if (it != versions.end()) | 
|  | { | 
|  | // NOLINTNEXTLINE(misc-redundant-expression) | 
|  | if (it->second->isFunctional() && ACTIVE_BMC_MAX_ALLOWED > 1) | 
|  | { | 
|  | error( | 
|  | "Version ({VERSIONID}) is currently running on the BMC; unable to remove.", | 
|  | "VERSIONID", entryId); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | // First call resetUbootEnvVars() so that the BMC points to a valid image to | 
|  | // boot from. If resetUbootEnvVars() is called after the image is actually | 
|  | // deleted from the BMC flash, there'd be a time window where the BMC would | 
|  | // be pointing to a non-existent image to boot from. | 
|  | // Need to remove the entries from the activations map before that call so | 
|  | // that resetUbootEnvVars() doesn't use the version to be deleted. | 
|  | auto iteratorActivations = activations.find(entryId); | 
|  | if (iteratorActivations == activations.end()) | 
|  | { | 
|  | error( | 
|  | "Failed to find version ({VERSIONID}) in item updater activations map; unable to remove.", | 
|  | "VERSIONID", entryId); | 
|  | } | 
|  | else | 
|  | { | 
|  | removeAssociations(iteratorActivations->second->path); | 
|  | iteratorActivations->second->deleteImageManagerObject(); | 
|  | this->activations.erase(entryId); | 
|  | } | 
|  | ItemUpdater::resetUbootEnvVars(); | 
|  |  | 
|  | if (it != versions.end()) | 
|  | { | 
|  | auto flashId = it->second->path(); | 
|  |  | 
|  | // Delete version data if it has been installed on flash (path is not | 
|  | // the upload directory) | 
|  | if (flashId.find(IMG_UPLOAD_DIR) == std::string::npos) | 
|  | { | 
|  | removeReadOnlyPartition(entryId); | 
|  | removePersistDataDirectory(flashId); | 
|  | helper.clearEntry(flashId); | 
|  | } | 
|  | else if (useUpdateDBusInterface) | 
|  | { | 
|  | fs::path imageDirPath = flashId; | 
|  | if (fs::exists(imageDirPath)) | 
|  | { | 
|  | info("Erasing image temporary folder {ID}", "ID", flashId); | 
|  | fs::remove_all(imageDirPath); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Removing entry in versions map | 
|  | this->versions.erase(entryId); | 
|  | } | 
|  |  | 
|  | // Removing entry in updateManagers map | 
|  | auto updateManagerIt = updateManagers.find(entryId); | 
|  | if (updateManagerIt != updateManagers.end()) | 
|  | { | 
|  | updateManagers.erase(entryId); | 
|  | } | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | void ItemUpdater::deleteAll() | 
|  | { | 
|  | std::vector<std::string> deletableVersions; | 
|  |  | 
|  | for (const auto& versionIt : versions) | 
|  | { | 
|  | if (!versionIt.second->isFunctional()) | 
|  | { | 
|  | deletableVersions.push_back(versionIt.first); | 
|  | } | 
|  | } | 
|  |  | 
|  | for (const auto& deletableIt : deletableVersions) | 
|  | { | 
|  | ItemUpdater::erase(deletableIt); | 
|  | } | 
|  |  | 
|  | helper.cleanup(); | 
|  | } | 
|  |  | 
|  | ItemUpdater::ActivationStatus ItemUpdater::validateSquashFSImage( | 
|  | const std::string& filePath) | 
|  | { | 
|  | bool valid = true; | 
|  |  | 
|  | // Record the images which are being updated | 
|  | // First check for the fullimage, then check for images with partitions | 
|  | imageUpdateList.push_back(bmcFullImages); | 
|  | valid = checkImage(filePath, imageUpdateList); | 
|  | if (!valid) | 
|  | { | 
|  | imageUpdateList.clear(); | 
|  | imageUpdateList.assign(bmcImages.begin(), bmcImages.end()); | 
|  | valid = checkImage(filePath, imageUpdateList); | 
|  | if (!valid) | 
|  | { | 
|  | error("Failed to find the needed BMC images."); | 
|  | return ItemUpdater::ActivationStatus::invalid; | 
|  | } | 
|  | } | 
|  |  | 
|  | return ItemUpdater::ActivationStatus::ready; | 
|  | } | 
|  |  | 
|  | void ItemUpdater::savePriority(const std::string& versionId, uint8_t value) | 
|  | { | 
|  | auto flashId = versions.find(versionId)->second->path(); | 
|  | storePriority(flashId, value); | 
|  | helper.setEntry(flashId, value); | 
|  | } | 
|  |  | 
|  | void ItemUpdater::freePriority(uint8_t value, const std::string& versionId) | 
|  | { | 
|  | std::map<std::string, uint8_t> priorityMap; | 
|  |  | 
|  | // Insert the requested version and priority, it may not exist yet. | 
|  | priorityMap.insert(std::make_pair(versionId, value)); | 
|  |  | 
|  | for (const auto& intf : activations) | 
|  | { | 
|  | if (intf.second->redundancyPriority) | 
|  | { | 
|  | priorityMap.insert(std::make_pair( | 
|  | intf.first, intf.second->redundancyPriority->priority())); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Lambda function to compare 2 priority values, use <= to allow duplicates | 
|  | typedef std::function<bool(std::pair<std::string, uint8_t>, | 
|  | std::pair<std::string, uint8_t>)> | 
|  | cmpPriority; | 
|  | cmpPriority cmpPriorityFunc = | 
|  | [](const std::pair<std::string, uint8_t>& priority1, | 
|  | const std::pair<std::string, uint8_t>& priority2) { | 
|  | return priority1.second <= priority2.second; | 
|  | }; | 
|  |  | 
|  | // Sort versions by ascending priority | 
|  | std::set<std::pair<std::string, uint8_t>, cmpPriority> prioritySet( | 
|  | priorityMap.begin(), priorityMap.end(), cmpPriorityFunc); | 
|  |  | 
|  | auto freePriorityValue = value; | 
|  | for (auto& element : prioritySet) | 
|  | { | 
|  | if (element.first == versionId) | 
|  | { | 
|  | continue; | 
|  | } | 
|  | if (element.second == freePriorityValue) | 
|  | { | 
|  | ++freePriorityValue; | 
|  | auto it = activations.find(element.first); | 
|  | it->second->redundancyPriority->sdbusPriority(freePriorityValue); | 
|  | } | 
|  | } | 
|  |  | 
|  | auto lowestVersion = prioritySet.begin()->first; | 
|  | if (value == prioritySet.begin()->second) | 
|  | { | 
|  | lowestVersion = versionId; | 
|  | } | 
|  | updateUbootEnvVars(lowestVersion); | 
|  | } | 
|  |  | 
|  | void ItemUpdater::reset() | 
|  | { | 
|  | phosphor::software::updater::Helper::factoryReset(); | 
|  |  | 
|  | info("BMC factory reset will take effect upon reboot."); | 
|  | } | 
|  |  | 
|  | void ItemUpdater::removeReadOnlyPartition(const std::string& versionId) | 
|  | { | 
|  | auto flashId = versions.find(versionId)->second->path(); | 
|  | helper.removeVersion(flashId); | 
|  | } | 
|  |  | 
|  | bool ItemUpdater::fieldModeEnabled(bool value) | 
|  | { | 
|  | // enabling field mode is intended to be one way: false -> true | 
|  | if (value && !control::FieldMode::fieldModeEnabled()) | 
|  | { | 
|  | control::FieldMode::fieldModeEnabled(value); | 
|  |  | 
|  | auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, | 
|  | SYSTEMD_INTERFACE, "StartUnit"); | 
|  | method.append("obmc-flash-bmc-setenv@fieldmode\\x3dtrue.service", | 
|  | "replace"); | 
|  | bus.call_noreply(method); | 
|  |  | 
|  | method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, | 
|  | SYSTEMD_INTERFACE, "StopUnit"); | 
|  | method.append("usr-local.mount", "replace"); | 
|  | bus.call_noreply(method); | 
|  |  | 
|  | std::vector<std::string> usrLocal = {"usr-local.mount"}; | 
|  |  | 
|  | method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, | 
|  | SYSTEMD_INTERFACE, "MaskUnitFiles"); | 
|  | method.append(usrLocal, false, true); | 
|  | bus.call_noreply(method); | 
|  | } | 
|  | else if (!value && control::FieldMode::fieldModeEnabled()) | 
|  | { | 
|  | elog<NotAllowed>(xyz::openbmc_project::common::NotAllowed::REASON( | 
|  | "FieldMode is not allowed to be cleared")); | 
|  | } | 
|  |  | 
|  | return control::FieldMode::fieldModeEnabled(); | 
|  | } | 
|  |  | 
|  | void ItemUpdater::restoreFieldModeStatus() | 
|  | { | 
|  | // The fieldmode u-boot environment variable may not exist since it is not | 
|  | // part of the default environment, run fw_printenv with 2>&1 to ignore the | 
|  | // error message in the journal "Error: "fieldmode" not defined" | 
|  | std::pair<int, std::string> ret = | 
|  | utils::execute("/sbin/fw_printenv", "-n", "fieldmode", "2>&1"); | 
|  |  | 
|  | if (ret.first != 0) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // truncate any extra characters off the end to compare against a "true" str | 
|  | std::string result = ret.second.substr(0, 4); | 
|  | if (result == "true") | 
|  | { | 
|  | ItemUpdater::fieldModeEnabled(true); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ItemUpdater::setBMCInventoryPath() | 
|  | { | 
|  | auto depth = 0; | 
|  | auto mapperCall = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH, | 
|  | MAPPER_INTERFACE, "GetSubTreePaths"); | 
|  |  | 
|  | mapperCall.append(INVENTORY_PATH); | 
|  | mapperCall.append(depth); | 
|  | std::vector<std::string> filter = {BMC_INVENTORY_INTERFACE}; | 
|  | mapperCall.append(filter); | 
|  |  | 
|  | try | 
|  | { | 
|  | auto response = bus.call(mapperCall); | 
|  |  | 
|  | using ObjectPaths = std::vector<std::string>; | 
|  | ObjectPaths result; | 
|  | response.read(result); | 
|  |  | 
|  | if (!result.empty()) | 
|  | { | 
|  | bmcInventoryPath = result.front(); | 
|  | } | 
|  | } | 
|  | catch (const sdbusplus::exception_t& e) | 
|  | { | 
|  | error("Error in mapper GetSubTreePath: {ERROR}", "ERROR", e); | 
|  | return; | 
|  | } | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | void ItemUpdater::createActiveAssociation(const std::string& path) | 
|  | { | 
|  | assocs.emplace_back( | 
|  | std::make_tuple(ACTIVE_FWD_ASSOCIATION, ACTIVE_REV_ASSOCIATION, path)); | 
|  | associations(assocs); | 
|  | } | 
|  |  | 
|  | void ItemUpdater::createFunctionalAssociation(const std::string& path) | 
|  | { | 
|  | assocs.emplace_back(std::make_tuple(FUNCTIONAL_FWD_ASSOCIATION, | 
|  | FUNCTIONAL_REV_ASSOCIATION, path)); | 
|  | associations(assocs); | 
|  | } | 
|  |  | 
|  | void ItemUpdater::createUpdateableAssociation(const std::string& path) | 
|  | { | 
|  | assocs.emplace_back(std::make_tuple(UPDATEABLE_FWD_ASSOCIATION, | 
|  | UPDATEABLE_REV_ASSOCIATION, path)); | 
|  | associations(assocs); | 
|  | } | 
|  |  | 
|  | void ItemUpdater::removeAssociations(const std::string& path) | 
|  | { | 
|  | for (auto iter = assocs.begin(); iter != assocs.end();) | 
|  | { | 
|  | if (std::get<2>(*iter) == path) | 
|  | { | 
|  | iter = assocs.erase(iter); | 
|  | associations(assocs); | 
|  | } | 
|  | else | 
|  | { | 
|  | ++iter; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | bool ItemUpdater::isLowestPriority(uint8_t value) | 
|  | { | 
|  | for (const auto& intf : activations) | 
|  | { | 
|  | if (intf.second->redundancyPriority) | 
|  | { | 
|  | if (intf.second->redundancyPriority->priority() < value) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void ItemUpdater::updateUbootEnvVars(const std::string& versionId) | 
|  | { | 
|  | auto it = versions.find(versionId); | 
|  | if (it == versions.end()) | 
|  | { | 
|  | return; | 
|  | } | 
|  | auto flashId = it->second->path(); | 
|  | helper.updateUbootVersionId(flashId); | 
|  | } | 
|  |  | 
|  | void ItemUpdater::resetUbootEnvVars() | 
|  | { | 
|  | decltype(activations.begin()->second->redundancyPriority->priority()) | 
|  | lowestPriority = std::numeric_limits<uint8_t>::max(); | 
|  | decltype(activations.begin()->second->versionId) lowestPriorityVersion; | 
|  | for (const auto& intf : activations) | 
|  | { | 
|  | if (!intf.second->redundancyPriority) | 
|  | { | 
|  | // Skip this version if the redundancyPriority is not initialized. | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (intf.second->redundancyPriority->priority() <= lowestPriority) | 
|  | { | 
|  | lowestPriority = intf.second->redundancyPriority->priority(); | 
|  | lowestPriorityVersion = intf.second->versionId; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Update the U-boot environment variable to point to the lowest priority | 
|  | updateUbootEnvVars(lowestPriorityVersion); | 
|  | } | 
|  |  | 
|  | void ItemUpdater::freeSpace([[maybe_unused]] const Activation& caller) | 
|  | { | 
|  | #ifdef BMC_STATIC_DUAL_IMAGE | 
|  | // For the golden image case, always remove the version on the primary side | 
|  | std::string versionIDtoErase; | 
|  | for (const auto& iter : activations) | 
|  | { | 
|  | if (iter.second->redundancyPriority && | 
|  | iter.second->redundancyPriority->priority() == 0) | 
|  | { | 
|  | versionIDtoErase = iter.second->versionId; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (!versionIDtoErase.empty()) | 
|  | { | 
|  | erase(versionIDtoErase); | 
|  | } | 
|  | else | 
|  | { | 
|  | warning("Failed to find version to erase"); | 
|  | } | 
|  | #else | 
|  | //  Versions with the highest priority in front | 
|  | std::priority_queue<std::pair<int, std::string>, | 
|  | std::vector<std::pair<int, std::string>>, | 
|  | std::less<std::pair<int, std::string>>> | 
|  | versionsPQ; | 
|  |  | 
|  | std::size_t count = 0; | 
|  | for (const auto& iter : activations) | 
|  | { | 
|  | if ((iter.second.get()->activation() == | 
|  | server::Activation::Activations::Active) || | 
|  | (iter.second.get()->activation() == | 
|  | server::Activation::Activations::Failed)) | 
|  | { | 
|  | count++; | 
|  | // Don't put the functional version on the queue since we can't | 
|  | // remove the "running" BMC version. | 
|  | // If ACTIVE_BMC_MAX_ALLOWED <= 1, there is only one active BMC, | 
|  | // so remove functional version as well. | 
|  | // Don't delete the the Activation object that called this function. | 
|  | if ((versions.find(iter.second->versionId) | 
|  | ->second->isFunctional() && | 
|  | ACTIVE_BMC_MAX_ALLOWED > 1) || | 
|  | (iter.second->versionId == caller.versionId)) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | // Failed activations don't have priority, assign them a large value | 
|  | // for sorting purposes. | 
|  | auto priority = 999; | 
|  | if (iter.second.get()->activation() == | 
|  | server::Activation::Activations::Active && | 
|  | iter.second->redundancyPriority) | 
|  | { | 
|  | priority = iter.second->redundancyPriority.get()->priority(); | 
|  | } | 
|  |  | 
|  | versionsPQ.push(std::make_pair(priority, iter.second->versionId)); | 
|  | } | 
|  | } | 
|  |  | 
|  | // If the number of BMC versions is over ACTIVE_BMC_MAX_ALLOWED -1, | 
|  | // remove the highest priority one(s). | 
|  | while ((count >= ACTIVE_BMC_MAX_ALLOWED) && (!versionsPQ.empty())) | 
|  | { | 
|  | erase(versionsPQ.top().second); | 
|  | versionsPQ.pop(); | 
|  | count--; | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  | void ItemUpdater::mirrorUbootToAlt() | 
|  | { | 
|  | helper.mirrorAlt(); | 
|  | } | 
|  |  | 
|  | bool ItemUpdater::checkImage(const std::string& filePath, | 
|  | const std::vector<std::string>& imageList) | 
|  | { | 
|  | bool valid = true; | 
|  |  | 
|  | for (auto& bmcImage : imageList) | 
|  | { | 
|  | fs::path file(filePath); | 
|  | file /= bmcImage; | 
|  | std::ifstream efile(file.c_str()); | 
|  | if (efile.good() != 1) | 
|  | { | 
|  | valid = false; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | return valid; | 
|  | } | 
|  |  | 
|  | #ifdef HOST_BIOS_UPGRADE | 
|  | void ItemUpdater::createBIOSObject() | 
|  | { | 
|  | std::string path = BIOS_OBJPATH; | 
|  | // Get version id from last item in the path | 
|  | auto pos = path.rfind('/'); | 
|  | if (pos == std::string::npos) | 
|  | { | 
|  | error("No version id found in object path {PATH}", "PATH", path); | 
|  | return; | 
|  | } | 
|  |  | 
|  | createActiveAssociation(path); | 
|  | createFunctionalAssociation(path); | 
|  | createUpdateableAssociation(path); | 
|  |  | 
|  | auto versionId = path.substr(pos + 1); | 
|  | auto version = "null"; | 
|  | AssociationList assocs; | 
|  | biosActivation = std::make_unique<Activation>( | 
|  | ctx, path, *this, versionId, server::Activation::Activations::Active, | 
|  | assocs); | 
|  | auto dummyErase = [](const std::string& /*entryId*/) { | 
|  | // Do nothing; | 
|  | }; | 
|  | biosVersion = std::make_unique<VersionClass>( | 
|  | bus, path, version, VersionPurpose::Host, "", "", | 
|  | std::vector<std::string>(), | 
|  | std::bind(dummyErase, std::placeholders::_1), ""); | 
|  | biosVersion->deleteObject = | 
|  | std::make_unique<phosphor::software::manager::Delete>( | 
|  | bus, path, *biosVersion); | 
|  |  | 
|  | if (useUpdateDBusInterface) | 
|  | { | 
|  | createUpdateObject(versionId, path); | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | void ItemUpdater::getRunningSlot() | 
|  | { | 
|  | // Check /run/media/slot to get the slot number | 
|  | constexpr auto slotFile = "/run/media/slot"; | 
|  | std::fstream f(slotFile, std::ios_base::in); | 
|  | f >> runningImageSlot; | 
|  | } | 
|  |  | 
|  | } // namespace updater | 
|  | } // namespace software | 
|  | } // namespace phosphor |