blob: 7cce844a90b830ff8176c6a35084a6d4edc56c04 [file]
#ifndef THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_UPDATE_SERVICE_H_
#define THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_UPDATE_SERVICE_H_
/*
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
#include <algorithm>
#include <array>
#include <cctype>
#include <chrono> // NOLINT
#include <cstddef>
#include <cstdint>
#include <filesystem> // NOLINT
#include <fstream>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>
#include "absl/strings/str_cat.h"
#include "boost/algorithm/string/case_conv.hpp" // NOLINT
#include "boost/system/error_code.hpp" // NOLINT
#include "boost/uuid/uuid_generators.hpp" // NOLINT
#include "boost/uuid/uuid_io.hpp" // NOLINT
#include "bmcweb_config.h"
#include "app.hpp"
#include "http_request.hpp"
#include "http_response.hpp"
#include "logging.hpp"
#include "utility.hpp"
#include "async_resp.hpp"
#include "dbus_utility.hpp"
#include "multipart_parser.hpp"
#include "error_messages.hpp"
#include "query.hpp"
#include "registries/privilege_registry.hpp"
#include "task_messages.hpp"
#include "dbus_utils.hpp"
#include "json_utils.hpp"
#include "sw_utils.hpp"
#include "other_software_service.hpp"
#include "task.hpp"
#include <nlohmann/json.hpp>
#include "managed_store.hpp"
#include "managed_store_types.hpp"
#include "sdbusplus/bus/match.hpp"
#include "sdbusplus/message.hpp"
#include "sdbusplus/message/native_types.hpp"
#include "sdbusplus/unpack_properties.hpp"
namespace redfish {
// Match signals added on software path
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher;
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher;
// Only allow one update at a time
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static bool fwUpdateInProgress = false;
// Timer for software available
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
inline static void cleanUp() {
fwUpdateInProgress = false;
fwUpdateMatcher = nullptr;
fwUpdateErrorMatcher = nullptr;
}
inline static void activateImage(const std::string& objPath,
const std::string& service) {
BMCWEB_LOG_DEBUG << "Activate image for " << objPath << " " << service;
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
nullptr,
[](const boost::system::error_code& errorCode) {
if (errorCode) {
BMCWEB_LOG_DEBUG << "error_code = " << errorCode;
BMCWEB_LOG_DEBUG << "error msg = " << errorCode.message();
}
},
service, objPath, "org.freedesktop.DBus.Properties", "Set",
"xyz.openbmc_project.Software.Activation", "RequestedActivation",
dbus::utility::DbusVariantType("xyz.openbmc_project.Software.Activation."
"RequestedActivations.Active"));
}
// Note that asyncResp can be either a valid pointer or nullptr. If nullptr
// then no asyncResp updates will occur
inline static void softwareInterfaceAdded(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
sdbusplus::message_t& m, task::Payload&& payload) {
dbus::utility::DBusInteracesMap interfacesProperties;
sdbusplus::message::object_path objPath;
m.read(objPath, interfacesProperties);
managedStore::ManagedObjectStoreContext context(asyncResp);
BMCWEB_LOG_DEBUG << "obj path = " << objPath.str;
for (const auto& interface : interfacesProperties) {
BMCWEB_LOG_DEBUG << "interface = " << interface.first;
if (interface.first == "xyz.openbmc_project.Software.Activation") {
// Retrieve service and activate
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Software.Activation"};
managedStore::GetManagedObjectStore()->getDbusObject(
objPath.str, interfaces, context,
[objPath, asyncResp, payload(std::move(payload))](
const boost::system::error_code& errorCode,
const std::vector<std::pair<
std::string, std::vector<std::string>>>& objInfo) mutable {
if (errorCode) {
BMCWEB_LOG_DEBUG << "error_code = " << errorCode;
BMCWEB_LOG_DEBUG << "error msg = " << errorCode.message();
if (asyncResp) {
messages::internalError(asyncResp->res);
}
cleanUp();
return;
}
// Ensure we only got one service back
if (objInfo.size() != 1) {
BMCWEB_LOG_ERROR << "Invalid Object Size " << objInfo.size();
if (asyncResp) {
messages::internalError(asyncResp->res);
}
cleanUp();
return;
}
// cancel timer only when
// xyz.openbmc_project.Software.Activation interface
// is added
fwAvailableTimer = nullptr;
activateImage(objPath.str, objInfo[0].first);
if (asyncResp) {
std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
[](const boost::system::error_code& ec,
sdbusplus::message_t& msg,
const std::shared_ptr<task::TaskData>& taskData) {
if (ec) {
return task::completed;
}
std::string iface;
dbus::utility::DBusPropertiesMap values;
std::string index = std::to_string(taskData->index);
msg.read(iface, values);
if (iface == "xyz.openbmc_project.Software.Activation") {
const std::string* state = nullptr;
for (const auto& property : values) {
if (property.first == "Activation") {
state = std::get_if<std::string>(&property.second);
if (state == nullptr) {
taskData->messages.emplace_back(
messages::internalError());
return task::completed;
}
}
}
if (state == nullptr) {
return !task::completed;
}
if (state->ends_with("Invalid") ||
state->ends_with("Failed")) {
taskData->state = "Exception";
taskData->status = "Warning";
taskData->messages.emplace_back(
messages::taskAborted(index));
return task::completed;
}
if (state->ends_with("Staged")) {
taskData->state = "Stopping";
taskData->messages.emplace_back(
messages::taskPaused(index));
// its staged, set a long timer to
// allow them time to complete the
// update (probably cycle the
// system) if this expires then
// task will be cancelled
taskData->extendTimer(std::chrono::hours(5));
return !task::completed;
}
if (state->ends_with("Active")) {
taskData->messages.emplace_back(
messages::taskCompletedOK(index));
taskData->state = "Completed";
return task::completed;
}
} else if (iface ==
"xyz.openbmc_project.Software."
"ActivationProgress") {
const uint8_t* progress = nullptr;
for (const auto& property : values) {
if (property.first == "Progress") {
progress = std::get_if<uint8_t>(&property.second);
if (progress == nullptr) {
taskData->messages.emplace_back(
messages::internalError());
return task::completed;
}
}
}
if (progress == nullptr) {
return !task::completed;
}
taskData->percentComplete = *progress;
taskData->messages.emplace_back(
messages::taskProgressChanged(index, *progress));
// if we're getting status updates it's
// still alive, update timer
taskData->extendTimer(std::chrono::minutes(5));
}
// as firmware update often results in a
// reboot, the task may never "complete"
// unless it is an error
return !task::completed;
},
"type='signal',interface='org.freedesktop.DBus.Properties',"
"member='PropertiesChanged',path='" +
objPath.str + "'");
task->startTimer(std::chrono::minutes(5));
task->populateResp(asyncResp->res);
task->payload.emplace(std::move(payload));
}
fwUpdateInProgress = false;
});
break;
}
}
}
// Note that asyncResp can be either a valid pointer or nullptr. If nullptr
// then no asyncResp updates will occur
inline static void monitorForSoftwareAvailable(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const crow::Request& req, const std::string& url,
int timeoutTimeSeconds = 25) {
#ifdef UNIT_TEST_BUILD
timeoutTimeSeconds = 0;
#endif
// Only allow one FW update at a time
if (fwUpdateInProgress) {
if (asyncResp) {
messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
}
return;
}
fwAvailableTimer =
std::make_unique<boost::asio::steady_timer>(*req.ioService);
fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
fwAvailableTimer->async_wait([asyncResp](
const boost::system::error_code& ec) {
cleanUp();
if (ec == boost::asio::error::operation_aborted) {
// expected, we were canceled before the timer completed.
return;
}
BMCWEB_LOG_ERROR << "Timed out waiting for firmware object being created";
BMCWEB_LOG_ERROR << "FW image may has already been uploaded to server";
if (ec) {
BMCWEB_LOG_ERROR << "Async_wait failed" << ec;
return;
}
if (asyncResp) {
redfish::messages::internalError(asyncResp->res);
}
});
task::Payload payload(req);
auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable {
BMCWEB_LOG_DEBUG << "Match fired";
softwareInterfaceAdded(asyncResp, m, std::move(payload));
};
fwUpdateInProgress = true;
fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>(
*(managedStore::GetManagedObjectStore()
->GetDeprecatedThreadUnsafeSystemBus()),
"interface='org.freedesktop.DBus.ObjectManager',type='signal',"
"member='InterfacesAdded',path='/xyz/openbmc_project/software'",
callback);
fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>(
*(managedStore::GetManagedObjectStore()
->GetDeprecatedThreadUnsafeSystemBus()),
"interface='org.freedesktop.DBus.ObjectManager',type='signal',"
"member='InterfacesAdded',"
"path='/xyz/openbmc_project/logging'",
[asyncResp, url](sdbusplus::message_t& m) {
std::vector<std::pair<std::string, dbus::utility::DBusPropertiesMap>>
interfacesProperties;
sdbusplus::message::object_path objPath;
m.read(objPath, interfacesProperties);
BMCWEB_LOG_DEBUG << "obj path = " << objPath.str;
for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>&
interface : interfacesProperties) {
if (interface.first == "xyz.openbmc_project.Logging.Entry") {
for (const std::pair<std::string, dbus::utility::DbusVariantType>&
value : interface.second) {
if (value.first != "Message") {
continue;
}
const std::string* type = std::get_if<std::string>(&value.second);
if (type == nullptr) {
// if this was our message, timeout will cover it
return;
}
fwAvailableTimer = nullptr;
if (*type ==
"xyz.openbmc_project.Software.Image.Error.UnTarFailure") {
redfish::messages::invalidUpload(asyncResp->res, url,
"Invalid archive");
} else if (*type ==
"xyz.openbmc_project.Software.Image.Error."
"ManifestFileFailure") {
redfish::messages::invalidUpload(asyncResp->res, url,
"Invalid manifest");
} else if (*type ==
"xyz.openbmc_project.Software.Image.Error."
"ImageFailure") {
redfish::messages::invalidUpload(asyncResp->res, url,
"Invalid image format");
} else if (*type ==
"xyz.openbmc_project.Software.Version.Error."
"AlreadyExists") {
redfish::messages::invalidUpload(
asyncResp->res, url, "Image version already exists");
redfish::messages::resourceAlreadyExists(
asyncResp->res, "UpdateService", "Version",
"uploaded version");
} else if (*type ==
"xyz.openbmc_project.Software.Image.Error."
"BusyFailure") {
redfish::messages::resourceExhaustion(asyncResp->res, url);
} else {
redfish::messages::internalError(asyncResp->res);
}
}
}
}
});
}
inline void handleUpdateServiceActionsSimpleUpdatePost(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
std::optional<std::string> transferProtocol;
std::string imageURI;
BMCWEB_LOG_DEBUG << "Enter UpdateService.SimpleUpdate doPost";
// User can pass in both TransferProtocol and ImageURI parameters or
// they can pass in just the ImageURI with the transfer protocol
// embedded within it.
// 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
// 2) ImageURI:tftp://1.1.1.1/myfile.bin
if (!json_util::readJsonAction(req, asyncResp->res, "TransferProtocol",
transferProtocol, "ImageURI", imageURI)) {
BMCWEB_LOG_DEBUG << "Missing TransferProtocol or ImageURI parameter";
return;
}
if (!transferProtocol) {
// Must be option 2
// Verify ImageURI has transfer protocol in it
size_t separator = imageURI.find(':');
if ((separator == std::string::npos) ||
((separator + 1) > imageURI.size())) {
messages::actionParameterValueTypeError(
asyncResp->res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
BMCWEB_LOG_ERROR << "ImageURI missing transfer protocol: " << imageURI;
return;
}
transferProtocol = imageURI.substr(0, separator);
// Ensure protocol is upper case for a common comparison path
// below
boost::to_upper(*transferProtocol);
BMCWEB_LOG_DEBUG << "Encoded transfer protocol " << *transferProtocol;
// Adjust imageURI to not have the protocol on it for parsing
// below
// ex. tftp://1.1.1.1/myfile.bin -> 1.1.1.1/myfile.bin
imageURI = imageURI.substr(separator + 3);
BMCWEB_LOG_DEBUG << "Adjusted imageUri " << imageURI;
}
// OpenBMC currently only supports TFTP
if (*transferProtocol != "TFTP") {
messages::actionParameterNotSupported(asyncResp->res, "TransferProtocol",
"UpdateService.SimpleUpdate");
BMCWEB_LOG_ERROR << "Request incorrect protocol parameter: "
<< *transferProtocol;
return;
}
// Format should be <IP or Hostname>/<file> for imageURI
size_t separator = imageURI.find('/');
if ((separator == std::string::npos) || ((separator + 1) > imageURI.size())) {
messages::actionParameterValueTypeError(
asyncResp->res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
BMCWEB_LOG_ERROR << "Invalid ImageURI: " << imageURI;
return;
}
std::string tftpServer = imageURI.substr(0, separator);
std::string fwFile = imageURI.substr(separator + 1);
BMCWEB_LOG_DEBUG << "Server: " << tftpServer + " File: " << fwFile;
// Setup callback for when new software detected
// Give TFTP 10 minutes to complete
monitorForSoftwareAvailable(
asyncResp, req,
"/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate", 600);
// TFTP can take up to 10 minutes depending on image size and
// connection speed. Return to caller as soon as the TFTP operation
// has been started. The callback above will ensure the activate
// is started once the download has completed
redfish::messages::success(asyncResp->res);
// Call TFTP service
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[](const boost::system::error_code& ec) {
if (ec) {
// messages::internalError(asyncResp->res);
cleanUp();
BMCWEB_LOG_DEBUG << "error_code = " << ec;
BMCWEB_LOG_DEBUG << "error msg = " << ec.message();
} else {
BMCWEB_LOG_DEBUG << "Call to DownloaViaTFTP Success";
}
},
"xyz.openbmc_project.Software.Download", "/xyz/openbmc_project/software",
"xyz.openbmc_project.Common.TFTP", "DownloadViaTFTP", fwFile, tftpServer);
BMCWEB_LOG_DEBUG << "Exit UpdateService.SimpleUpdate doPost";
}
/**
* UpdateServiceActionsSimpleUpdate class supports handle POST method for
* SimpleUpdate action.
*/
inline void requestRoutesUpdateServiceActionsSimpleUpdate(App& app) {
BMCWEB_ROUTE(app,
"/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
.privileges(redfish::privileges::postUpdateService)
.methods(boost::beast::http::verb::post)(std::bind_front(
handleUpdateServiceActionsSimpleUpdatePost, std::ref(app)));
}
#if ENABLE_FII_UPDATE_SERVICE
inline bool imgExists(
const std::filesystem::path& p,
std::filesystem::file_status s = std::filesystem::file_status{}) {
if (std::filesystem::status_known(s) ? std::filesystem::exists(s)
: std::filesystem::exists(p))
return true;
else
return false;
}
inline void FxnMonitorUploadImage(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const crow::Request& req) {
if (fwUpdateInProgress) {
messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
return;
}
task::Payload payload(req);
std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
[](const boost::system::error_code& ec, sdbusplus::message_t&,
const std::shared_ptr<task::TaskData>& taskData) {
std::string SuccessFlag = "/tmp/UpdateSuccess";
std::string FailFlag = "/tmp/UpdateFail";
std::string index = std::to_string(taskData->index);
if (ec) {
return task::completed;
}
fwUpdateInProgress = true;
taskData->percentComplete = 100;
if (imgExists(SuccessFlag)) {
taskData->state = "Completed";
std::remove(SuccessFlag.c_str());
fwUpdateInProgress = false;
return task::completed;
} else if (imgExists(FailFlag)) {
taskData->state = "Exception";
taskData->status = "Warning";
std::remove(FailFlag.c_str());
fwUpdateInProgress = false;
return task::completed;
}
return !task::completed;
},
"");
fwUpdateInProgress = false;
task->startTimer(std::chrono::minutes(10));
task->populateResp(asyncResp->res);
task->payload.emplace(std::move(payload));
}
inline void FxnUploadImageFile(crow::Response& res, std::string_view body,
std::string_view imgPath) {
std::filesystem::path filepath(imgPath);
BMCWEB_LOG_DEBUG << "Writing file to " << filepath;
std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
std::ofstream::trunc);
// set the permission of the file to 640
std::filesystem::perms permission =
std::filesystem::perms::owner_read | std::filesystem::perms::group_read;
std::filesystem::permissions(filepath, permission);
out << body;
if (out.bad()) {
messages::internalError(res);
cleanUp();
} else {
messages::success(res);
}
}
#endif
inline void uploadImageFile(crow::Response& res, std::string_view body) {
std::filesystem::path filepath(
"/tmp/images/" +
boost::uuids::to_string(boost::uuids::random_generator()()));
BMCWEB_LOG_DEBUG << "Writing file to " << filepath;
std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
std::ofstream::trunc);
// set the permission of the file to 640
std::filesystem::perms permission =
std::filesystem::perms::owner_read | std::filesystem::perms::group_read;
std::filesystem::permissions(filepath, permission);
out << body;
if (out.bad()) {
messages::internalError(res);
cleanUp();
}
}
inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& applyTime) {
std::string applyTimeNewVal;
if (applyTime == "Immediate") {
applyTimeNewVal =
"xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
} else if (applyTime == "OnReset") {
applyTimeNewVal =
"xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
} else {
BMCWEB_LOG_INFO
<< "ApplyTime value is not in the list of acceptable values";
messages::propertyValueNotInList(asyncResp->res, applyTime, "ApplyTime");
return;
}
// Set the requested image apply time value
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp](const boost::system::error_code ec) {
if (ec) {
BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
messages::internalError(asyncResp->res);
return;
}
messages::success(asyncResp->res);
},
"xyz.openbmc_project.Settings",
"/xyz/openbmc_project/software/apply_time",
"org.freedesktop.DBus.Properties", "Set",
"xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime",
dbus::utility::DbusVariantType{applyTimeNewVal});
}
inline void updateMultipartContext(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const MultipartParser& parser,
[[maybe_unused]] std::optional<std::reference_wrapper<const crow::Request>>
reqOpt) {
const std::string* uploadData = nullptr;
std::optional<std::string> applyTime = "OnReset";
bool targetFound = false;
#if ENABLE_FII_UPDATE_SERVICE
bool targetOtherComp = false;
std::string imgPath;
#endif
for (const FormPart& formpart : parser.mime_fields) {
boost::beast::http::fields::const_iterator it =
formpart.fields.find("Content-Disposition");
if (it == formpart.fields.end()) {
BMCWEB_LOG_ERROR << "Couldn't find Content-Disposition";
return;
}
BMCWEB_LOG_INFO << "Parsing value " << it->value();
// The construction parameters of param_list must start with `;`
size_t index = it->value().find(';');
if (index == std::string::npos) {
continue;
}
for (auto const& param :
boost::beast::http::param_list{it->value().substr(index)}) {
if (param.first != "name" || param.second.empty()) {
continue;
}
if (param.second == "UpdateParameters") {
std::vector<std::string> targets;
nlohmann::json content = nlohmann::json::parse(formpart.content);
if (!json_util::readJson(content, asyncResp->res, "Targets", targets,
"@Redfish.OperationApplyTime", applyTime)) {
return;
}
if (targets.size() != 1) {
messages::propertyValueFormatError(asyncResp->res, "Targets", "");
return;
}
if ((targets[0] != "/redfish/v1/Managers/bmc")
#if ENABLE_FII_UPDATE_SERVICE
&& (targets[0] != "/redfish/v1/Managers/bios") &&
(targets[0] != "/redfish/v1/Managers/cpld") &&
(targets[0] != "/redfish/v1/Managers/pwrseq")
#endif
) {
messages::propertyValueNotInList(asyncResp->res, "Targets/0",
targets[0]);
return;
}
targetFound = true;
#if ENABLE_FII_UPDATE_SERVICE
if ((targets[0] == "/redfish/v1/Managers/bios") ||
(targets[0] == "/redfish/v1/Managers/cpld") ||
(targets[0] == "/redfish/v1/Managers/pwrseq"))
targetOtherComp = true;
#endif
} else if (param.second == "UpdateFile") {
uploadData = &(formpart.content);
}
}
}
if (uploadData == nullptr) {
BMCWEB_LOG_ERROR << "Upload data is NULL";
messages::propertyMissing(asyncResp->res, "UpdateFile");
return;
}
if (!targetFound) {
messages::propertyMissing(asyncResp->res, "targets");
return;
}
#if ENABLE_FII_UPDATE_SERVICE
if (targetOtherComp) {
imgPath = "/run/uploadImage/uploadImage.tar";
FxnUploadImageFile(asyncResp->res, *uploadData, imgPath);
if (reqOpt) {
FxnMonitorUploadImage(asyncResp, *reqOpt);
}
} else {
setApplyTime(asyncResp, *applyTime);
// Setup callback for when new software detected
if (reqOpt) {
monitorForSoftwareAvailable(asyncResp, *reqOpt,
"/redfish/v1/UpdateService");
}
uploadImageFile(asyncResp->res, *uploadData);
}
#else
setApplyTime(asyncResp, *applyTime);
uploadImageFile(asyncResp->res, *uploadData);
#endif
}
inline void handleUpdateServicePost(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
BMCWEB_LOG_DEBUG << "doPost...";
#if !ENABLE_FII_UPDATE_SERVICE
// Setup callback for when new software detected
monitorForSoftwareAvailable(asyncResp, req, "/redfish/v1/UpdateService");
#endif
MultipartParser parser;
ParserError ec = parser.parse(req);
if (ec == ParserError::ERROR_BOUNDARY_FORMAT) {
// If the request didnt' contain boundary information, assume it was a
// POST binary payload.
uploadImageFile(asyncResp->res, req.body());
return;
}
if (ec != ParserError::PARSER_SUCCESS) {
// handle error
BMCWEB_LOG_ERROR << "MIME parse failed, ec : " << static_cast<int>(ec);
messages::internalError(asyncResp->res);
return;
}
#if ENABLE_FII_UPDATE_SERVICE
updateMultipartContext(asyncResp, parser, std::make_optional(std::ref(req)));
#else
updateMultipartContext(asyncResp, parser, std::nullopt);
#endif
}
inline void handleUpdateServiceGet(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
asyncResp->res.jsonValue["@odata.type"] =
"#UpdateService.v1_11_1.UpdateService";
asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
asyncResp->res.jsonValue["Id"] = "UpdateService";
asyncResp->res.jsonValue["Description"] = "Service for Software Update";
asyncResp->res.jsonValue["Name"] = "Update Service";
#ifdef BMCWEB_ENABLE_REDFISH_UPDATESERVICE_OLD_POST_URL
// See note about later on in this file about why this is neccesary
// This is "Wrong" per the standard, but is done temporarily to
// avoid noise in failing tests as people transition to having this
// option disabled
asyncResp->res.addHeader(boost::beast::http::field::allow,
"GET, PATCH, HEAD");
#endif
asyncResp->res.jsonValue["HttpPushUri"] = "/redfish/v1/UpdateService/update";
asyncResp->res.jsonValue["MultipartHttpPushUri"] =
"/redfish/v1/UpdateService/update";
// UpdateService cannot be disabled
asyncResp->res.jsonValue["ServiceEnabled"] = true;
asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
"/redfish/v1/UpdateService/FirmwareInventory";
// Get the MaxImageSizeBytes
asyncResp->res.jsonValue["MaxImageSizeBytes"] =
bmcwebHttpReqBodyLimitMb * 1024 * 1024;
#ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE
// Update Actions object.
nlohmann::json& updateSvcSimpleUpdate =
asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
updateSvcSimpleUpdate["target"] =
"/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] = {"TFTP"};
#endif
// Get the current ApplyTime value
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
dbus_utils::getProperty<std::string>(
"xyz.openbmc_project.Settings",
"/xyz/openbmc_project/software/apply_time",
"xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime",
requestContext,
[asyncResp](const boost::system::error_code& ec,
const std::string& applyTime) {
if (ec) {
BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
messages::internalError(asyncResp->res);
return;
}
// Store the ApplyTime Value
if (applyTime ==
"xyz.openbmc_project.Software.ApplyTime."
"RequestedApplyTimes.Immediate") {
asyncResp->res.jsonValue["HttpPushUriOptions"]["HttpPushUriApplyTime"]
["ApplyTime"] = "Immediate";
} else if (applyTime ==
"xyz.openbmc_project.Software.ApplyTime."
"RequestedApplyTimes.OnReset") {
asyncResp->res.jsonValue["HttpPushUriOptions"]["HttpPushUriApplyTime"]
["ApplyTime"] = "OnReset";
}
});
}
inline void handleUpdateServicePatch(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
BMCWEB_LOG_DEBUG << "doPatch...";
std::optional<nlohmann::json> pushUriOptions;
if (!json_util::readJsonPatch(req, asyncResp->res, "HttpPushUriOptions",
pushUriOptions)) {
return;
}
if (pushUriOptions) {
std::optional<nlohmann::json> pushUriApplyTime;
if (!json_util::readJson(*pushUriOptions, asyncResp->res,
"HttpPushUriApplyTime", pushUriApplyTime)) {
return;
}
if (pushUriApplyTime) {
std::optional<std::string> applyTime;
if (!json_util::readJson(*pushUriApplyTime, asyncResp->res, "ApplyTime",
applyTime)) {
return;
}
if (applyTime) {
setApplyTime(asyncResp, *applyTime);
}
}
}
}
inline void requestRoutesUpdateService(App& app) {
BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
.privileges(redfish::privileges::getUpdateService)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleUpdateServiceGet, std::ref(app)));
BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
.privileges(redfish::privileges::patchUpdateService)
.methods(boost::beast::http::verb::patch)(
std::bind_front(handleUpdateServicePatch, std::ref(app)));
// The "old" behavior of the update service URI causes redfish-service validator
// failures when the Allow header is supported, given that in the spec,
// UpdateService does not allow POST. in openbmc, we unfortunately reused that
// resource as our HttpPushUri as well. A number of services, including the
// openbmc tests, and documentation have hardcoded that erroneous API, instead
// of relying on HttpPushUri as the spec requires. This option will exist
// temporarily to allow the old behavior until Q4 2022, at which time it will be
// removed.
#ifdef BMCWEB_ENABLE_REDFISH_UPDATESERVICE_OLD_POST_URL
BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
.privileges(redfish::privileges::postUpdateService)
.methods(boost::beast::http::verb::post)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
asyncResp->res.addHeader(
boost::beast::http::field::warning,
"299 - \"POST to /redfish/v1/UpdateService is deprecated. Use "
"the value contained within HttpPushUri.\"");
handleUpdateServicePost(app, req, asyncResp);
});
#endif
BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/")
.privileges(redfish::privileges::postUpdateService)
.methods(boost::beast::http::verb::post)(
std::bind_front(handleUpdateServicePost, std::ref(app)));
}
inline void handleSoftwareInventoryCollectionGet(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
asyncResp->res.jsonValue["@odata.type"] =
"#SoftwareInventoryCollection.SoftwareInventoryCollection";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/UpdateService/FirmwareInventory";
asyncResp->res.jsonValue["Name"] = "Software Inventory Collection";
// Note that only firmware levels associated with a device
// are stored under /xyz/openbmc_project/software therefore
// to ensure only real FirmwareInventory items are returned,
// this full object path must be used here as input to
// mapper
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Software.Version"};
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getSubTree(
"/xyz/openbmc_project/software", 0, interfaces, context,
[asyncResp](const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec) {
messages::internalError(asyncResp->res);
return;
}
auto foundMembers = asyncResp->res.jsonValue.find("Members");
if (foundMembers == asyncResp->res.jsonValue.end()) {
// With Redfish aggregation, top level collections can have
// their Members array already created if a satellite response
// has been processed first. In that case we don't want to
// clobber the aggregated data
asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
asyncResp->res.jsonValue["Members@odata.count"] = 0;
}
for (const auto& obj : subtree) {
sdbusplus::message::object_path path(obj.first);
std::string swId = path.filename();
if (swId.empty()) {
messages::internalError(asyncResp->res);
BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!";
return;
}
nlohmann::json& members = asyncResp->res.jsonValue["Members"];
nlohmann::json::object_t member;
member["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "UpdateService", "FirmwareInventory", swId);
members.push_back(std::move(member));
asyncResp->res.jsonValue["Members@odata.count"] = members.size();
}
});
}
inline void requestRoutesSoftwareInventoryCollection(App& app) {
BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
.privileges(redfish::privileges::getSoftwareInventoryCollection)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleSoftwareInventoryCollectionGet, std::ref(app)));
}
/*
* Get the appropriate system name based on the BIOS object path.
*
* The system name is formed by getting the integer at the end of the object
* path and appending it to the string "system".
*
* Arguments:
* path: dbus object path for the BIOS software, e.g.
* /xyz/openbmc_project/software/bios_active,
* /xyz/openbmc_project/software/bios_active_1
*
* Returns:
* systemName: system name for the appropriate system URI, e.g. system,
* system1, system2
*/
inline static std::string getSystemNameFromBiosPath(std::string_view path) {
std::string systemName = "system";
sdbusplus::message::object_path dbusPath((std::string(path)));
std::string softwareId = dbusPath.filename();
/*
* Find the integer at the end of the object path. It's preceded by an
* underscore, e.g. as in bios_active_1. But note that single-host machines
* won't have this integer at all.
*/
size_t underscorePos = softwareId.rfind('_');
if (underscorePos != std::string::npos &&
underscorePos + 1 < softwareId.size()) {
std::string suffix = softwareId.substr(underscorePos + 1);
bool isDigit = !suffix.empty() &&
std::all_of(suffix.begin(), suffix.end(), ::isdigit);
if (isDigit) {
/* Append the integer suffix. */
systemName.append(suffix);
}
}
return systemName;
}
/* Fill related item links (i.e. bmc, bios) in for inventory */
inline static void getRelatedItems(
const std::shared_ptr<bmcweb::AsyncResp>& aResp, std::string_view path,
const std::string& purpose) {
if (purpose == sw_util::bmcPurpose) {
nlohmann::json& relatedItem = aResp->res.jsonValue["RelatedItem"];
nlohmann::json::object_t item;
item["@odata.id"] = "/redfish/v1/Managers/bmc";
relatedItem.push_back(std::move(item));
aResp->res.jsonValue["RelatedItem@odata.count"] = relatedItem.size();
} else if (purpose == sw_util::biosPurpose) {
nlohmann::json& relatedItem = aResp->res.jsonValue["RelatedItem"];
nlohmann::json::object_t item;
std::string systemName = getSystemNameFromBiosPath(path);
item["@odata.id"] =
absl::StrCat("/redfish/v1/Systems/", systemName, "/Bios");
relatedItem.push_back(std::move(item));
aResp->res.jsonValue["RelatedItem@odata.count"] = relatedItem.size();
} else if (purpose == sw_util::otherPurpose) {
getRelatedItemsOthers(aResp, path);
} else {
BMCWEB_LOG_ERROR << "Unknown software purpose " << purpose;
}
}
inline std::optional<std::string> softwareVersionPurpose(
const std::string& purpose) {
if (purpose ==
"xyz.openbmc_project.Software.Version.VersionPurpose.Unknown") {
return "Unknown";
}
if (purpose == "xyz.openbmc_project.Software.Version.VersionPurpose.Other") {
return "Other";
}
if (purpose == "xyz.openbmc_project.Software.Version.VersionPurpose.System") {
return "System";
}
if (purpose == "xyz.openbmc_project.Software.Version.VersionPurpose.BMC") {
return "BMC";
}
if (purpose == "xyz.openbmc_project.Software.Version.VersionPurpose.Host") {
return "Host";
}
if (purpose == "xyz.openbmc_project.Software.Version.VersionPurpose.PSU") {
return "PSU";
}
return std::nullopt;
}
inline static void setWriteProtected(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& service, const std::string& path,
const bool writeProtected) {
managedStore::GetManagedObjectStore()->setProperty(
service, path, "xyz.openbmc_project.Software.Settings", "WriteProtected",
writeProtected, [asyncResp](const boost::system::error_code ec) {
if (ec) {
BMCWEB_LOG_ERROR << "setWriteProtected D-Bus responses error: " << ec;
messages::internalError(asyncResp->res);
return;
}
messages::success(asyncResp->res);
});
}
inline void getSoftwareVersion(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& service, const std::string& path,
const std::string& swId) {
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getAllProperties(
service, path, "xyz.openbmc_project.Software.Version", context,
[asyncResp, swId, path](
const boost::system::error_code& errorCode,
const dbus::utility::DBusPropertiesMap& propertiesList) {
if (errorCode) {
messages::internalError(asyncResp->res);
return;
}
const std::string* swInvPurpose = nullptr;
const std::string* version = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
swInvPurpose, "Version", version);
if (!success) {
messages::internalError(asyncResp->res);
return;
}
if (swInvPurpose == nullptr) {
BMCWEB_LOG_DEBUG << "Can't find property \"Purpose\"!";
messages::internalError(asyncResp->res);
return;
}
BMCWEB_LOG_DEBUG << "swInvPurpose = " << *swInvPurpose;
if (version == nullptr) {
BMCWEB_LOG_DEBUG << "Can't find property \"Version\"!";
messages::internalError(asyncResp->res);
return;
}
asyncResp->res.jsonValue["Version"] = *version;
asyncResp->res.jsonValue["Id"] = swId;
// swInvPurpose is of format:
// xyz.openbmc_project.Software.Version.VersionPurpose.ABC
// Translate this to "ABC image"
size_t endDesc = swInvPurpose->rfind('.');
if (endDesc == std::string::npos) {
messages::internalError(asyncResp->res);
return;
}
endDesc++;
if (endDesc >= swInvPurpose->size()) {
messages::internalError(asyncResp->res);
return;
}
std::string formatDesc = swInvPurpose->substr(endDesc);
asyncResp->res.jsonValue["Description"] = formatDesc + " image";
getRelatedItems(asyncResp, path, *swInvPurpose);
});
}
inline void handleSoftwareInventoryGet(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& param) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
std::shared_ptr<std::string> swId = std::make_shared<std::string>(param);
asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "UpdateService", "FirmwareInventory", *swId);
constexpr std::array<std::string_view, 3> interfaces = {
"xyz.openbmc_project.Software.Version",
"xyz.openbmc_project.Software.Activation",
"xyz.openbmc_project.Software.Settings",
};
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getSubTree(
"/xyz/openbmc_project/software", 0, interfaces, context,
[asyncResp, swId](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
BMCWEB_LOG_DEBUG << "doGet callback...";
if (ec) {
messages::internalError(asyncResp->res);
return;
}
// Ensure we find our input swId, otherwise return an error
bool found = false;
for (const std::pair<
std::string,
std::vector<std::pair<std::string, std::vector<std::string>>>>&
obj : subtree) {
// Extract the software ID from the Redfish object URI.
size_t objSwIdPos = obj.first.rfind("/");
std::string objSwId =
objSwIdPos == std::string::npos
? obj.first
: obj.first.substr(objSwIdPos + 1);
if (objSwId != *swId) {
continue;
}
if (obj.second.empty()) {
continue;
}
found = true;
for (const auto& [serviceName, interfaceList] : obj.second) {
for (const std::string& interface : interfaceList) {
if (interface == "xyz.openbmc_project.Software.Version") {
found = true;
getSoftwareVersion(asyncResp, serviceName, obj.first, *swId);
}
if (interface == "xyz.openbmc_project.Software.Activation") {
sw_util::getSwStatus(asyncResp, swId, serviceName);
}
if (interface == "xyz.openbmc_project.Software.Settings") {
sw_util::getSoftwareSettings(asyncResp, obj.second[0].first,
obj.first);
}
}
}
}
if (!found) {
BMCWEB_LOG_ERROR << "Input swID " << *swId << " not found!";
messages::resourceMissingAtURI(
asyncResp->res,
crow::utility::urlFromPieces("redfish", "v1", "UpdateService",
"FirmwareInventory", *swId));
return;
}
asyncResp->res.jsonValue["@odata.type"] =
"#SoftwareInventory.v1_3_0.SoftwareInventory";
asyncResp->res.jsonValue["Name"] = "Software Inventory";
asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
asyncResp->res.jsonValue["Updateable"] = false;
sw_util::getSwUpdatableStatus(asyncResp, swId);
});
}
inline void handleSoftwareInventoryPatch(
[[maybe_unused]] App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& swId) {
bool writeProtected = false;
if (!json_util::readJsonPatch(req, asyncResp->res, "WriteProtected",
writeProtected)) {
BMCWEB_LOG_DEBUG << "WriteProtected is not in the patch input";
return;
}
constexpr std::array<std::string_view, 1> interfaces{
"xyz.openbmc_project.Software.Settings"};
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getSubTree(
"/", 0, interfaces, context,
[asyncResp, swId, writeProtected](
const boost::system::error_code ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec) {
messages::internalError(asyncResp->res);
return;
}
BMCWEB_LOG_DEBUG << "Patching FirmwareInventory with .WriteProtected.";
for (const std::pair<std::string, dbus::utility::MapperServiceMap>&
obj : subtree) {
if (sdbusplus::message::object_path(obj.first).filename() != swId ||
obj.second.empty()) {
continue;
}
setWriteProtected(asyncResp, obj.second[0].first, obj.first,
writeProtected);
return;
}
messages::resourceNotFound(
asyncResp->res, "#SoftwareInventory.v1_3_0.SoftwareInventory",
swId);
});
}
inline void requestRoutesSoftwareInventory(App& app) {
BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
.privileges(redfish::privileges::getSoftwareInventory)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleSoftwareInventoryGet, std::ref(app)));
BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
.privileges(redfish::privileges::patchSoftwareInventory)
.methods(boost::beast::http::verb::patch)(
std::bind_front(handleSoftwareInventoryPatch, std::ref(app)));
}
} // namespace redfish
#endif // THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_UPDATE_SERVICE_H_