blob: 3fc64ae1c3bdc038b8ccdf17b1720f84be653390 [file] [log] [blame]
#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 <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/log.hpp>
#include <xyz/openbmc_project/Software/Version/server.hpp>
#include <filesystem>
#include <fstream>
#include <queue>
#include <string>
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<Activation> 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<ActivationUbi>(
bus, path, *this, versionId, extVersion, activationStatus, assocs);
}
std::unique_ptr<Version> 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<Version>(
bus, objPath, *this, versionId, versionString, versionPurpose, filePath,
std::bind(&ItemUpdaterUbi::erase, this, std::placeholders::_1));
version->deleteObject = std::make_unique<Delete>(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<level::ERR>("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<level::ERR>("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<level::ERR>("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<ActivationUbi>(
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<uint8_t>::max();
if (!restoreFromFile(id, priority))
{
log<level::ERR>("Unable to restore priority from file.",
entry("VERSIONID=%s", id.c_str()));
}
activations.find(id)->second->redundancyPriority =
std::make_unique<RedundancyPriorityUbi>(
bus, path, *(activations.find(id)->second), priority);
}
// Create Version instance for this version.
auto versionPtr = std::make_unique<Version>(
bus, path, *this, id, version, purpose, "",
std::bind(&ItemUpdaterUbi::erase, this, std::placeholders::_1));
versionPtr->deleteObject = std::make_unique<Delete>(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<level::ERR>("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<level::ERR>("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::pair<int, std::string>,
std::vector<std::pair<int, std::string>>,
std::greater<std::pair<int, std::string>>>
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::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)
{
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 <id> 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 GARD 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