| #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 |