#include "config.h" #include "image_manager.hpp" #include "version.hpp" #include "watch.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace phosphor { namespace software { namespace manager { PHOSPHOR_LOG2_USING; using namespace phosphor::logging; using namespace sdbusplus::error::xyz::openbmc_project::software::image; 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; explicit RemovablePath(const fs::path& path) : path(path) {} ~RemovablePath() { if (!path.empty()) { std::error_code ec; fs::remove_all(path, ec); } } RemovablePath(const RemovablePath& other) = delete; RemovablePath& operator=(const RemovablePath& other) = delete; RemovablePath(RemovablePath&&) = delete; RemovablePath& operator=(RemovablePath&&) = delete; }; namespace // anonymous { std::vector getSoftwareObjects(sdbusplus::bus_t& bus) { std::vector 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({VERSION_BUSNAME})); auto reply = bus.call(method); reply.read(paths); return paths; } } // namespace int Manager::processImage(const std::string& tarFilePath) { std::error_code ec; if (!fs::is_regular_file(tarFilePath, ec)) { error("Tarball {PATH} does not exist: {ERROR_MSG}", "PATH", tarFilePath, "ERROR_MSG", ec.message()); report(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(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, ec)) { error("No manifest file {PATH}: {ERROR_MSG}", "PATH", tarFilePath, "ERROR_MSG", ec.message()); report(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(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(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( ImageFail::FAIL("Machine name does not match"), ImageFail::PATH(manifestPath.string().c_str())); return -1; } } else { warning("No machine name in Manifest file"); report( 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(ManifestFail::PATH(tarFilePath.c_str())); return -1; } auto convertedPurpose = sdbusplus::message::convert_from_string( 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"); // Get CompatibleNames std::vector compatibleNames = Version::getRepeatedValues(manifestPath.string(), "CompatibleName"); // Compute id auto salt = std::to_string(randomGen()); auto id = Version::getId(version + salt); 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, ec); // Clear the path, so it does not attempt to remove a non-existing path tmpDirToRemove.path.clear(); // Create Version object auto versionPtr = std::make_unique( bus, objPath, version, purpose, extendedVersion, imageDirPath.string(), compatibleNames, std::bind(&Manager::erase, this, std::placeholders::_1), id); versionPtr->deleteObject = std::make_unique( 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(const std::string& entryId) { auto it = versions.find(entryId); if (it == versions.end()) { return; } // Delete image dir fs::path imageDirPath = (*(it->second)).path(); std::error_code ec; if (fs::exists(imageDirPath, ec)) { fs::remove_all(imageDirPath, ec); } this->versions.erase(entryId); } int Manager::unTar(const std::string& tarFilePath, const std::string& extractDirPath) { if (tarFilePath.empty()) { error("TarFilePath is empty"); report(UnTarFail::PATH(tarFilePath.c_str())); return -1; } if (extractDirPath.empty()) { error("ExtractDirPath is empty"); report(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(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(UnTarFail::PATH(tarFilePath.c_str())); return -1; } } else { error("fork() failed: {ERRNO}", "ERRNO", errno); report(UnTarFail::PATH(tarFilePath.c_str())); return -1; } return 0; } } // namespace manager } // namespace software } // namespace phosphor