#include "config.h" #include "item_updater_ubi.hpp" #include "activation_ubi.hpp" #include "serialize.hpp" #include "utils.hpp" #include "version.hpp" #include "xyz/openbmc_project/Common/error.hpp" #include #include #include #include #include #include #include namespace openpower { namespace software { namespace updater { // When you see server:: you know we're referencing our base class namespace server = sdbusplus::xyz::openbmc_project::Software::server; using namespace sdbusplus::xyz::openbmc_project::Common::Error; using namespace phosphor::logging; std::unique_ptr ItemUpdaterUbi::createActivationObject( const std::string& path, const std::string& versionId, const std::string& extVersion, sdbusplus::xyz::openbmc_project::Software::server::Activation::Activations activationStatus, AssociationList& assocs) { return std::make_unique( bus, path, *this, versionId, extVersion, activationStatus, assocs); } std::unique_ptr ItemUpdaterUbi::createVersionObject( const std::string& objPath, const std::string& versionId, const std::string& versionString, sdbusplus::xyz::openbmc_project::Software::server::Version::VersionPurpose versionPurpose, const std::string& filePath) { auto version = std::make_unique( bus, objPath, *this, versionId, versionString, versionPurpose, filePath, std::bind(&ItemUpdaterUbi::erase, this, std::placeholders::_1)); version->deleteObject = std::make_unique(bus, objPath, *version); return version; } bool ItemUpdaterUbi::validateImage(const std::string& path) { return validateSquashFSImage(path) == 0; } void ItemUpdaterUbi::processPNORImage() { // Read pnor.toc from folders under /media/ // to get Active Software Versions. for (const auto& iter : std::filesystem::directory_iterator(MEDIA_DIR)) { auto activationState = server::Activation::Activations::Active; static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX); static const auto PNOR_RW_PREFIX_LEN = strlen(PNOR_RW_PREFIX); // Check if the PNOR_RO_PREFIX is the prefix of the iter.path if (0 == iter.path().native().compare(0, PNOR_RO_PREFIX_LEN, PNOR_RO_PREFIX)) { // The versionId is extracted from the path // for example /media/pnor-ro-2a1022fe. auto id = iter.path().native().substr(PNOR_RO_PREFIX_LEN); auto pnorTOC = iter.path() / PNOR_TOC_FILE; if (!std::filesystem::is_regular_file(pnorTOC)) { log("Failed to read pnorTOC.", entry("FILENAME=%s", pnorTOC.c_str())); ItemUpdaterUbi::erase(id); continue; } auto keyValues = Version::getValue( pnorTOC, {{"version", ""}, {"extended_version", ""}}); auto& version = keyValues.at("version"); if (version.empty()) { log("Failed to read version from pnorTOC", entry("FILENAME=%s", pnorTOC.c_str())); activationState = server::Activation::Activations::Invalid; } auto& extendedVersion = keyValues.at("extended_version"); if (extendedVersion.empty()) { log("Failed to read extendedVersion from pnorTOC", entry("FILENAME=%s", pnorTOC.c_str())); activationState = server::Activation::Activations::Invalid; } auto purpose = server::Version::VersionPurpose::Host; auto path = std::filesystem::path(SOFTWARE_OBJPATH) / id; AssociationList associations = {}; if (activationState == server::Activation::Activations::Active) { // Create an association to the host inventory item associations.emplace_back(std::make_tuple( ACTIVATION_FWD_ASSOCIATION, ACTIVATION_REV_ASSOCIATION, HOST_INVENTORY_PATH)); // Create an active association since this image is active createActiveAssociation(path); } // All updateable firmware components must expose the updateable // association. createUpdateableAssociation(path); // Create Activation instance for this version. activations.insert( std::make_pair(id, std::make_unique( bus, path, *this, id, extendedVersion, activationState, associations))); // If Active, create RedundancyPriority instance for this version. if (activationState == server::Activation::Activations::Active) { uint8_t priority = std::numeric_limits::max(); if (!restoreFromFile(id, priority)) { log("Unable to restore priority from file.", entry("VERSIONID=%s", id.c_str())); } activations.find(id)->second->redundancyPriority = std::make_unique( bus, path, *(activations.find(id)->second), priority); } // Create Version instance for this version. auto versionPtr = std::make_unique( bus, path, *this, id, version, purpose, "", std::bind(&ItemUpdaterUbi::erase, this, std::placeholders::_1)); versionPtr->deleteObject = std::make_unique(bus, path, *versionPtr); versions.insert(std::make_pair(id, std::move(versionPtr))); } else if (0 == iter.path().native().compare(0, PNOR_RW_PREFIX_LEN, PNOR_RW_PREFIX)) { auto id = iter.path().native().substr(PNOR_RW_PREFIX_LEN); auto roDir = PNOR_RO_PREFIX + id; if (!std::filesystem::is_directory(roDir)) { log("No corresponding read-only volume found.", entry("DIRNAME=%s", roDir.c_str())); ItemUpdaterUbi::erase(id); } } } // Look at the RO symlink to determine if there is a functional image auto id = determineId(PNOR_RO_ACTIVE_PATH); if (!id.empty()) { updateFunctionalAssociation(id); } return; } int ItemUpdaterUbi::validateSquashFSImage(const std::string& filePath) { auto file = std::filesystem::path(filePath) / squashFSImage; if (std::filesystem::is_regular_file(file)) { return 0; } else { log("Failed to find the SquashFS image."); return -1; } } void ItemUpdaterUbi::removeReadOnlyPartition(const std::string& versionId) { auto serviceFile = "obmc-flash-bios-ubiumount-ro@" + versionId + ".service"; // Remove the read-only partitions. auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, SYSTEMD_INTERFACE, "StartUnit"); method.append(serviceFile, "replace"); bus.call_noreply(method); } void ItemUpdaterUbi::removeReadWritePartition(const std::string& versionId) { auto serviceFile = "obmc-flash-bios-ubiumount-rw@" + versionId + ".service"; // Remove the read-write partitions. auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, SYSTEMD_INTERFACE, "StartUnit"); method.append(serviceFile, "replace"); bus.call_noreply(method); } void ItemUpdaterUbi::reset() { utils::hiomapdSuspend(bus); constexpr static auto patchDir = "/usr/local/share/pnor"; if (std::filesystem::is_directory(patchDir)) { for (const auto& iter : std::filesystem::directory_iterator(patchDir)) { std::filesystem::remove_all(iter); } } // Clear the read-write partitions. for (const auto& it : activations) { auto rwDir = PNOR_RW_PREFIX + it.first; if (std::filesystem::is_directory(rwDir)) { for (const auto& iter : std::filesystem::directory_iterator(rwDir)) { std::filesystem::remove_all(iter); } } } // Clear the preserved partition, except for SECBOOT that contains keys // provisioned for the system. if (std::filesystem::is_directory(PNOR_PRSV)) { for (const auto& iter : std::filesystem::directory_iterator(PNOR_PRSV)) { auto secbootPartition = "SECBOOT"; if (iter.path().stem() == secbootPartition) { continue; } std::filesystem::remove_all(iter); } } utils::hiomapdResume(bus); } bool ItemUpdaterUbi::isVersionFunctional(const std::string& versionId) { if (!std::filesystem::exists(PNOR_RO_ACTIVE_PATH)) { return false; } std::filesystem::path activeRO = std::filesystem::read_symlink(PNOR_RO_ACTIVE_PATH); if (!std::filesystem::is_directory(activeRO)) { return false; } if (activeRO.string().find(versionId) == std::string::npos) { return false; } // active PNOR is the version we're checking return true; } void ItemUpdaterUbi::freePriority(uint8_t value, const std::string& versionId) { // Versions with the lowest priority in front std::priority_queue, std::vector>, std::greater>> versionsPQ; for (const auto& intf : activations) { if (intf.second->redundancyPriority) { versionsPQ.push(std::make_pair( intf.second->redundancyPriority.get()->priority(), intf.second->versionId)); } } while (!versionsPQ.empty()) { if (versionsPQ.top().first == value && versionsPQ.top().second != versionId) { // Increase priority by 1 and update its value ++value; storeToFile(versionsPQ.top().second, value); auto it = activations.find(versionsPQ.top().second); it->second->redundancyPriority.get()->sdbusplus::xyz:: openbmc_project::Software::server::RedundancyPriority::priority( value); } versionsPQ.pop(); } } bool ItemUpdaterUbi::erase(std::string entryId) { if (!ItemUpdater::erase(entryId)) { return false; } // Remove priority persistence file removeFile(entryId); // Removing read-only and read-write partitions removeReadWritePartition(entryId); removeReadOnlyPartition(entryId); return true; } void ItemUpdaterUbi::deleteAll() { auto chassisOn = isChassisOn(); for (const auto& activationIt : activations) { if (isVersionFunctional(activationIt.first) && chassisOn) { continue; } else { ItemUpdaterUbi::erase(activationIt.first); } } // Remove any remaining pnor-ro- or pnor-rw- volumes that do not match // the current version. auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, SYSTEMD_INTERFACE, "StartUnit"); method.append("obmc-flash-bios-cleanup.service", "replace"); bus.call_noreply(method); } // TODO: openbmc/openbmc#1402 Monitor flash usage bool ItemUpdaterUbi::freeSpace() { bool isSpaceFreed = false; // Versions with the highest priority in front std::priority_queue, std::vector>, std::less>> versionsPQ; std::size_t count = 0; for (const auto& iter : activations) { if (iter.second.get()->activation() == server::Activation::Activations::Active) { count++; // Don't put the functional version on the queue since we can't // remove the "running" PNOR version if it allows multiple PNORs // But removing functional version if there is only one PNOR. if (ACTIVE_PNOR_MAX_ALLOWED > 1 && isVersionFunctional(iter.second->versionId)) { continue; } versionsPQ.push(std::make_pair( iter.second->redundancyPriority.get()->priority(), iter.second->versionId)); } } // If the number of PNOR versions is over ACTIVE_PNOR_MAX_ALLOWED -1, // remove the highest priority one(s). while ((count >= ACTIVE_PNOR_MAX_ALLOWED) && (!versionsPQ.empty())) { erase(versionsPQ.top().second); versionsPQ.pop(); count--; isSpaceFreed = true; } return isSpaceFreed; } std::string ItemUpdaterUbi::determineId(const std::string& symlinkPath) { if (!std::filesystem::exists(symlinkPath)) { return {}; } auto target = std::filesystem::canonical(symlinkPath).string(); // check to make sure the target really exists if (!std::filesystem::is_regular_file(target + "/" + PNOR_TOC_FILE)) { return {}; } // Get the image from the symlink target // for example /media/ro-2a1022fe static const auto PNOR_RO_PREFIX_LEN = strlen(PNOR_RO_PREFIX); return target.substr(PNOR_RO_PREFIX_LEN); } void GardResetUbi::reset() { // The GUARD partition is currently misspelled "GUARD." This file path will // need to be updated in the future. auto path = std::filesystem::path(PNOR_PRSV_ACTIVE_PATH); path /= "GUARD"; utils::hiomapdSuspend(bus); if (std::filesystem::is_regular_file(path)) { std::filesystem::remove(path); } utils::hiomapdResume(bus); } } // namespace updater } // namespace software } // namespace openpower