blob: 1282ae0a731032325f551ea09bf4d5d7e121f0b8 [file] [log] [blame]
#include "tlbmc/redfish/routes/update_service.h"
#include <cstdlib>
#include <filesystem> // NOLINT
#include <fstream>
#include <iterator>
#include <string>
#include <string_view>
#include <system_error> // NOLINT
#include <utility>
#include "absl/functional/bind_front.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/substitute.h"
#include "nlohmann/json.hpp"
#include "tlbmc/redfish/app.h"
#include "tlbmc/redfish/request.h"
#include "tlbmc/redfish/response.h"
namespace milotic_tlbmc::update_service {
namespace {
// Handler for /redfish/v1/UpdateService
void HandleUpdateService(const RedfishRequest& req, RedfishResponse& resp) {
resp.SetKeyInJsonBody("/@odata.id", "/redfish/v1/UpdateService");
resp.SetKeyInJsonBody("/@odata.type", "#UpdateService.v1_8_0.UpdateService");
resp.SetKeyInJsonBody("/Id", "UpdateService");
resp.SetKeyInJsonBody("/Name", "Update Service");
resp.SetKeyInJsonBody("/ServiceEnabled", true);
resp.SetKeyInJsonBody("/FirmwareInventory/@odata.id",
"/redfish/v1/UpdateService/FirmwareInventory");
resp.SetKeyInJsonBody("/MultipartHttpPushUri",
"/redfish/v1/UpdateService/MultipartUpdate/");
}
// Handler for /redfish/v1/UpdateService/FirmwareInventory
void HandleFirmwareInventory(const std::string& root_path,
const RedfishRequest& req, RedfishResponse& resp) {
resp.SetKeyInJsonBody("/@odata.id",
"/redfish/v1/UpdateService/FirmwareInventory");
resp.SetKeyInJsonBody(
"/@odata.type",
"#SoftwareInventoryCollection.SoftwareInventoryCollection");
resp.SetKeyInJsonBody("/Name", "SoftwareInventoryCollection");
resp.SetKeyInJsonBody("/Id", "FirmwareInventory");
resp.SetKeyInJsonBody("/Description", "Firmware Inventory Collection.");
const std::filesystem::path inventory_root =
std::filesystem::path(root_path) / "run/install/inventory/";
nlohmann::json::array_t members;
std::error_code ec;
if (std::filesystem::exists(inventory_root, ec) && !ec &&
std::filesystem::is_directory(inventory_root, ec) && !ec) {
for (const auto& entry :
std::filesystem::directory_iterator(inventory_root, ec)) {
if (!ec) {
if (entry.is_directory(ec) && !ec &&
std::filesystem::exists(entry.path() / "inventory.json", ec) &&
!ec) {
nlohmann::json::object_t member;
member["@odata.id"] =
absl::StrCat("/redfish/v1/UpdateService/FirmwareInventory/",
entry.path().filename().string());
members.push_back(std::move(member));
}
}
}
}
if (ec) {
resp.SetToInternalError(absl::StrCat(
"Failed to iterate over inventory directory: ", ec.message()));
return;
}
resp.SetKeyInJsonBody("/Members", members);
resp.SetKeyInJsonBody("/Members@odata.count", members.size());
}
void HandleFirmwareInventoryMember(const std::string& root_path,
const RedfishRequest& req,
RedfishResponse& resp,
const std::string& member) {
resp.SetKeyInJsonBody(
"/@odata.id",
absl::StrCat("/redfish/v1/UpdateService/FirmwareInventory/", member));
resp.SetKeyInJsonBody("/@odata.type",
"#SoftwareInventory.v1_2_2.SoftwareInventory");
resp.SetKeyInJsonBody("/Id", member);
resp.SetKeyInJsonBody("/Name", member);
std::filesystem::path inventory_file_path = std::filesystem::path(root_path) /
"run/install/inventory" / member /
"inventory.json";
std::ifstream inventory_file(inventory_file_path);
if (!inventory_file.is_open()) {
resp.SetToInternalError(absl::StrCat("Failed to open inventory file: ",
inventory_file_path.c_str()));
return;
}
std::string content((std::istreambuf_iterator<char>(inventory_file)),
std::istreambuf_iterator<char>());
nlohmann::json inventory_json =
nlohmann::json::parse(content, nullptr, false);
if (inventory_json.is_discarded()) {
resp.SetKeyInJsonBody("/Version", "unknown");
resp.SetKeyInJsonBody("/Description", "Failed activation check");
return;
}
resp.SetKeyInJsonBody("/Version", inventory_json["Version"]);
resp.SetKeyInJsonBody("/Description", inventory_json["Description"]);
}
} // namespace
namespace internal {
void HandleMultipartUpdate(std::string_view bundle_file_path_prefix,
const RedfishRequest& req, RedfishResponse& resp) {
constexpr std::string_view kBundleFilePath =
"mnt/luks-mmcblk0_fs/firmware_bundle.tar.gz";
constexpr std::string_view kInstallOngoingFilePath =
"run/install/install_ongoing";
std::filesystem::path bundle_file_path = bundle_file_path_prefix;
bundle_file_path /= kBundleFilePath;
std::filesystem::path install_ongoing_file_path = bundle_file_path_prefix;
install_ongoing_file_path /= kInstallOngoingFilePath;
if (std::error_code ec;
std::filesystem::exists(install_ongoing_file_path, ec) && !ec) {
resp.SetToConflict("Install is ongoing, please wait for it to complete.");
return;
}
std::string_view client_hash_base64 =
req.GetHeaderValue("Multipart-SHA256-ClientSide");
std::string client_hash;
if (!client_hash_base64.empty() &&
!absl::Base64Unescape(client_hash_base64, &client_hash)) {
resp.SetToInternalError("Failed to unescape client hash");
return;
}
std::string_view server_hash =
req.GetHeaderValue("Multipart-SHA256-ServerSide");
// Verify hash first if available
if (!client_hash.empty() && client_hash != server_hash) {
resp.SetToInternalError(
absl::Substitute("Hash mismatch! client side $0, server side $1",
client_hash, server_hash));
return;
}
std::error_code ec;
if (std::filesystem::rename(req.Body(), bundle_file_path, ec); ec) {
resp.SetToInternalError(
absl::Substitute("Failed to move file from $0 to $1: $2", req.Body(),
bundle_file_path.c_str(), ec.message()));
return;
}
// We ignore the return value of the system call and rely on task service to
// report failure if any.
std::system("systemctl start --no-block install-service.service");
resp.SetToAccepted("/redfish/v1/TaskService/Tasks/FirmwareBundleUpdate");
resp.SetKeyInJsonBody("/@odata.id",
"/redfish/v1/TaskService/Tasks/FirmwareBundleUpdate");
resp.SetKeyInJsonBody("/@odata.type", "#Task.v1_4_2.Task");
resp.SetKeyInJsonBody("/TaskState", "New");
resp.SetKeyInJsonBody("/PercentComplete", 0);
resp.SetKeyInJsonBody("/Id", "FirmwareBundleUpdate");
resp.SetKeyInJsonBody("/Name", "Firmware Bundle Update");
}
} // namespace internal
void RegisterRoutes(RedfishApp& app) {
TLBMC_ROUTE(app, "/redfish/v1/UpdateService/")
.methods(boost::beast::http::verb::get)(HandleUpdateService);
TLBMC_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
.methods(boost::beast::http::verb::get)(
absl::bind_front(HandleFirmwareInventory, "/"));
TLBMC_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
.methods(boost::beast::http::verb::get)(
absl::bind_front(HandleFirmwareInventoryMember, "/"));
TLBMC_ROUTE(app, "/redfish/v1/UpdateService/MultipartUpdate/")
.methods(boost::beast::http::verb::post)(
absl::bind_front(internal::HandleMultipartUpdate, "/"));
}
void RegisterRoutesForUnitTest(
RedfishApp& app, const std::string& install_inventory_path_prefix) {
TLBMC_ROUTE(app, "/redfish/v1/UpdateService/")
.methods(boost::beast::http::verb::get)(HandleUpdateService);
TLBMC_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
.methods(boost::beast::http::verb::get)(absl::bind_front(
HandleFirmwareInventory, std::string(install_inventory_path_prefix)));
TLBMC_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
.methods(boost::beast::http::verb::get)(
absl::bind_front(HandleFirmwareInventoryMember,
std::string(install_inventory_path_prefix)));
TLBMC_ROUTE(app, "/redfish/v1/UpdateService/MultipartUpdate/")
.methods(boost::beast::http::verb::post)(
absl::bind_front(internal::HandleMultipartUpdate,
std::string(install_inventory_path_prefix)));
}
} // namespace milotic_tlbmc::update_service