blob: a9a5a694af3d0ee08f49fae73642e97124332e1f [file] [log] [blame]
#include "config.h"
#include "image_manager.hpp"
#include "version.hpp"
#include "watch.hpp"
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <elog-errors.hpp>
#include <phosphor-logging/elog.hpp>
#include <phosphor-logging/lg2.hpp>
#include <xyz/openbmc_project/Software/Image/error.hpp>
#include <algorithm>
#include <cstring>
#include <filesystem>
#include <string>
namespace phosphor
{
namespace software
{
namespace manager
{
PHOSPHOR_LOG2_USING;
using namespace phosphor::logging;
using namespace sdbusplus::xyz::openbmc_project::Software::Image::Error;
namespace Software = phosphor::logging::xyz::openbmc_project::Software;
using ManifestFail = Software::Image::ManifestFileFailure;
using UnTarFail = Software::Image::UnTarFailure;
using InternalFail = Software::Image::InternalFailure;
using ImageFail = Software::Image::ImageFailure;
namespace fs = std::filesystem;
struct RemovablePath
{
fs::path path;
RemovablePath(const fs::path& path) : path(path)
{}
~RemovablePath()
{
if (!path.empty())
{
std::error_code ec;
fs::remove_all(path, ec);
}
}
};
namespace // anonymous
{
std::vector<std::string> getSoftwareObjects(sdbusplus::bus::bus& bus)
{
std::vector<std::string> paths;
auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
MAPPER_INTERFACE, "GetSubTreePaths");
method.append(SOFTWARE_OBJPATH);
method.append(0); // Depth 0 to search all
method.append(std::vector<std::string>({VERSION_BUSNAME}));
auto reply = bus.call(method);
reply.read(paths);
return paths;
}
} // namespace
int Manager::processImage(const std::string& tarFilePath)
{
if (!fs::is_regular_file(tarFilePath))
{
error("Tarball {PATH} does not exist", "PATH", tarFilePath);
report<ManifestFileFailure>(ManifestFail::PATH(tarFilePath.c_str()));
return -1;
}
RemovablePath tarPathRemove(tarFilePath);
fs::path tmpDirPath(std::string{IMG_UPLOAD_DIR});
tmpDirPath /= "imageXXXXXX";
auto tmpDir = tmpDirPath.string();
// Create a tmp dir to extract tarball.
if (!mkdtemp(tmpDir.data()))
{
error("Error ({ERRNO}) occurred during mkdtemp", "ERRNO", errno);
report<InternalFailure>(InternalFail::FAIL("mkdtemp"));
return -1;
}
tmpDirPath = tmpDir;
RemovablePath tmpDirToRemove(tmpDirPath);
fs::path manifestPath = tmpDirPath;
manifestPath /= MANIFEST_FILE_NAME;
// Untar tarball into the tmp dir
auto rc = unTar(tarFilePath, tmpDirPath.string());
if (rc < 0)
{
error("Error ({RC}) occurred during untar", "RC", rc);
return -1;
}
// Verify the manifest file
if (!fs::is_regular_file(manifestPath))
{
error("No manifest file {PATH}", "PATH", tarFilePath);
report<ManifestFileFailure>(ManifestFail::PATH(tarFilePath.c_str()));
return -1;
}
// Get version
auto version = Version::getValue(manifestPath.string(), "version");
if (version.empty())
{
error("Unable to read version from manifest file {PATH}", "PATH",
tarFilePath);
report<ManifestFileFailure>(ManifestFail::PATH(tarFilePath.c_str()));
return -1;
}
// Get running machine name
std::string currMachine = Version::getBMCMachine(OS_RELEASE_FILE);
if (currMachine.empty())
{
auto path = OS_RELEASE_FILE;
error("Failed to read machine name from osRelease: {PATH}", "PATH",
path);
report<ImageFailure>(ImageFail::FAIL("Failed to read machine name"),
ImageFail::PATH(path));
return -1;
}
// Get machine name for image to be upgraded
std::string machineStr =
Version::getValue(manifestPath.string(), "MachineName");
if (!machineStr.empty())
{
if (machineStr != currMachine)
{
error(
"BMC upgrade: Machine name doesn't match: {CURRENT_MACHINE} vs {NEW_MACHINE}",
"CURRENT_MACHINE", currMachine, "NEW_MACHINE", machineStr);
report<ImageFailure>(
ImageFail::FAIL("Machine name does not match"),
ImageFail::PATH(manifestPath.string().c_str()));
return -1;
}
}
else
{
warning("No machine name in Manifest file");
report<ImageFailure>(
ImageFail::FAIL("MANIFEST is missing machine name"),
ImageFail::PATH(manifestPath.string().c_str()));
}
// Get purpose
auto purposeString = Version::getValue(manifestPath.string(), "purpose");
if (purposeString.empty())
{
error("Unable to read purpose from manifest file {PATH}", "PATH",
tarFilePath);
report<ManifestFileFailure>(ManifestFail::PATH(tarFilePath.c_str()));
return -1;
}
auto convertedPurpose =
sdbusplus::message::convert_from_string<Version::VersionPurpose>(
purposeString);
if (!convertedPurpose)
{
error(
"Failed to convert manifest purpose ({PURPOSE}) to enum; setting to Unknown.",
"PURPOSE", purposeString);
}
auto purpose = convertedPurpose.value_or(Version::VersionPurpose::Unknown);
// Get ExtendedVersion
std::string extendedVersion =
Version::getValue(manifestPath.string(), "ExtendedVersion");
// Compute id
auto id = Version::getId(version);
fs::path imageDirPath = std::string{IMG_UPLOAD_DIR};
imageDirPath /= id;
auto objPath = std::string{SOFTWARE_OBJPATH} + '/' + id;
// This service only manages the uploaded versions, and there could be
// active versions on D-Bus that is not managed by this service.
// So check D-Bus if there is an existing version.
auto allSoftwareObjs = getSoftwareObjects(bus);
auto it =
std::find(allSoftwareObjs.begin(), allSoftwareObjs.end(), objPath);
if (versions.find(id) == versions.end() && it == allSoftwareObjs.end())
{
// Rename the temp dir to image dir
fs::rename(tmpDirPath, imageDirPath);
// Clear the path, so it does not attemp to remove a non-existing path
tmpDirToRemove.path.clear();
// Create Version object
auto versionPtr = std::make_unique<Version>(
bus, objPath, version, purpose, extendedVersion,
imageDirPath.string(),
std::bind(&Manager::erase, this, std::placeholders::_1));
versionPtr->deleteObject =
std::make_unique<phosphor::software::manager::Delete>(bus, objPath,
*versionPtr);
versions.insert(std::make_pair(id, std::move(versionPtr)));
}
else
{
info("Software Object with the same version ({VERSION}) already exists",
"VERSION", id);
}
return 0;
}
void Manager::erase(std::string entryId)
{
auto it = versions.find(entryId);
if (it == versions.end())
{
return;
}
if (it->second->isFunctional())
{
error(
"Version {VERSION} is currently running on the BMC; unable to remove",
"VERSION", entryId);
return;
}
// Delete image dir
fs::path imageDirPath = (*(it->second)).path();
if (fs::exists(imageDirPath))
{
fs::remove_all(imageDirPath);
}
this->versions.erase(entryId);
}
int Manager::unTar(const std::string& tarFilePath,
const std::string& extractDirPath)
{
if (tarFilePath.empty())
{
error("TarFilePath is empty");
report<UnTarFailure>(UnTarFail::PATH(tarFilePath.c_str()));
return -1;
}
if (extractDirPath.empty())
{
error("ExtractDirPath is empty");
report<UnTarFailure>(UnTarFail::PATH(extractDirPath.c_str()));
return -1;
}
info("Untaring {PATH} to {EXTRACTIONDIR}", "PATH", tarFilePath,
"EXTRACTIONDIR", extractDirPath);
int status = 0;
pid_t pid = fork();
if (pid == 0)
{
// child process
execl("/bin/tar", "tar", "-xf", tarFilePath.c_str(), "-C",
extractDirPath.c_str(), (char*)0);
// execl only returns on fail
error("Failed to execute untar on {PATH}", "PATH", tarFilePath);
report<UnTarFailure>(UnTarFail::PATH(tarFilePath.c_str()));
return -1;
}
else if (pid > 0)
{
waitpid(pid, &status, 0);
if (WEXITSTATUS(status))
{
error("Failed ({STATUS}) to untar file {PATH}", "STATUS", status,
"PATH", tarFilePath);
report<UnTarFailure>(UnTarFail::PATH(tarFilePath.c_str()));
return -1;
}
}
else
{
error("fork() failed: {ERRNO}", "ERRNO", errno);
report<UnTarFailure>(UnTarFail::PATH(tarFilePath.c_str()));
return -1;
}
return 0;
}
} // namespace manager
} // namespace software
} // namespace phosphor