blob: ca3064f3e53ad2369ecf61df3480c12a9bbf5983 [file]
#ifndef THIRD_PARTY_GBMCWEB_REDFISH_CORE_INCLUDE_UTILS_SW_UTILS_H_
#define THIRD_PARTY_GBMCWEB_REDFISH_CORE_INCLUDE_UTILS_SW_UTILS_H_
#include <algorithm>
#include <array>
#include <cctype>
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "boost/system/error_code.hpp" // NOLINT
#include "logging.hpp"
#include "utility.hpp"
#include "async_resp.hpp"
#include "dbus_utility.hpp"
#include "error_messages.hpp"
#include "generated/enums/resource.hpp"
#include "dbus_utils.hpp"
#include <nlohmann/json.hpp>
#include "managed_store.hpp"
#include "managed_store_types.hpp"
#include "sdbusplus/message/native_types.hpp"
#include "sdbusplus/unpack_properties.hpp"
namespace redfish {
namespace sw_util {
/* @brief String that indicates a bios software instance */
constexpr const char* biosPurpose =
"xyz.openbmc_project.Software.Version.VersionPurpose.Host";
/* @brief String that indicates a BMC software instance */
constexpr const char* bmcPurpose =
"xyz.openbmc_project.Software.Version.VersionPurpose.BMC";
/* @brief String that indicates other firmware instance */
constexpr const char* otherPurpose =
"xyz.openbmc_project.Software.Version.VersionPurpose.Other";
enum class SwAssociation : std::uint8_t {
System = 0,
Manager,
Chassis,
Other,
};
inline std::string_view swAssociationToString(SwAssociation assoc) {
switch (assoc) {
case SwAssociation::System:
return "System";
case SwAssociation::Manager:
return "Manager";
case SwAssociation::Chassis:
return "Chassis";
case SwAssociation::Other:
return "Other";
}
return "Other";
}
/**
* @brief Populate the running software version and image links
*
* @param[i,o] aResp Async response object
* @param[i] swVersionPurpose Indicates what target to look for
* @param[i] activeVersionPropName Index in aResp->res.jsonValue to write
* the running software version to
* @param[i] populateLinkToImages Populate aResp->res "Links"
* "ActiveSoftwareImage" with a link to the running software image and
* "SoftwareImages" with a link to the all its software images
* @param[i] swAssociation What entity the software is associated with
* @param[i] resourceId Resource ID when checking association
*
* @return void
*/
inline void populateSoftwareInformation(
const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::string& swVersionPurpose,
const std::string& activeVersionPropName, const bool populateLinkToImages,
SwAssociation swAssociation, const std::string& resourceId = "") {
// Used later to determine running (known on Redfish as active) Sw images
managedStore::ManagedObjectStoreContext requestContext(aResp);
dbus_utils::getAssociationEndPoints(
"/xyz/openbmc_project/software/functional", requestContext,
[aResp, swVersionPurpose, activeVersionPropName, populateLinkToImages,
swAssociation,
resourceId](const boost::system::error_code& ec,
const dbus::utility::MapperEndPoints& functionalSw) {
BMCWEB_LOG_DEBUG << "populateSoftwareInformation enter";
if (ec) {
BMCWEB_LOG_ERROR << "error_code = " << ec;
BMCWEB_LOG_ERROR << "error msg = " << ec.message();
messages::internalError(aResp->res);
return;
}
if (functionalSw.empty()) {
// Could keep going and try to populate SoftwareImages but
// something is seriously wrong, so just fail
BMCWEB_LOG_ERROR << "Zero functional software in system";
messages::internalError(aResp->res);
return;
}
std::vector<std::string> functionalSwIds;
// example functionalSw:
// v as 2 "/xyz/openbmc_project/software/ace821ef"
// "/xyz/openbmc_project/software/230fb078"
for (const auto& sw : functionalSw) {
sdbusplus::message::object_path path(sw);
std::string leaf = path.filename();
if (leaf.empty()) {
continue;
}
functionalSwIds.push_back(leaf);
}
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Software.Version"};
managedStore::ManagedObjectStoreContext requestContext(aResp);
managedStore::GetManagedObjectStore()->getSubTree(
"/xyz/openbmc_project/software", 0, interfaces, requestContext,
[aResp, swVersionPurpose, activeVersionPropName,
populateLinkToImages, functionalSwIds, swAssociation, resourceId](
const boost::system::error_code& ec2,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec2) {
BMCWEB_LOG_ERROR << "error_code = " << ec2;
BMCWEB_LOG_ERROR << "error msg = " << ec2.message();
messages::internalError(aResp->res);
return;
}
BMCWEB_LOG_DEBUG << "Found " << subtree.size() << " images";
for (const std::pair<std::string,
std::vector<std::pair<
std::string, std::vector<std::string>>>>&
obj : subtree) {
sdbusplus::message::object_path path(obj.first);
std::string swId = path.filename();
if (swId.empty()) {
BMCWEB_LOG_ERROR << "Invalid software ID";
messages::appendToExtendedInfo(
aResp->res,
messages::subResourceSkipped(
obj.first, "Invalid software ID"));
continue;
}
bool runningImage = false;
// Look at Ids from
// /xyz/openbmc_project/software/functional
// to determine if this is a running image
if (std::find(functionalSwIds.begin(), functionalSwIds.end(),
swId) != functionalSwIds.end()) {
runningImage = true;
}
// Now grab its version info
managedStore::ManagedObjectStoreContext context(aResp);
managedStore::GetManagedObjectStore()->getAllProperties(
obj.second[0].first, obj.first,
"xyz.openbmc_project.Software.Version", context,
[aResp, swId, runningImage, swVersionPurpose,
activeVersionPropName, populateLinkToImages, swAssociation,
resourceId](const boost::system::error_code& ec3,
const dbus::utility::DBusPropertiesMap&
propertiesList) {
// This loop scans every Software.Version object and
// filters by Purpose. A failure on any single entry
// (broken peer service, EBADR from a code-update race,
// unrelated consumer of Software.Version like a storage
// firmware object, malformed properties) must not poison
// the response — the failed entry may not even be a
// Purpose we care about. Log, surface via
// @Message.ExtendedInfo so the operator can correlate,
// and continue; siblings of the requested Purpose can
// still be reported.
if (ec3) {
BMCWEB_LOG_ERROR << "Skipping " << swId
<< ": error_code = " << ec3
<< ", msg = " << ec3.message();
// EBADR is the expected race where the code-update
// service deletes the D-Bus object mid-request; no
// need to flag it to the client.
if (ec3.value() != EBADR) {
messages::appendToExtendedInfo(
aResp->res,
messages::subResourceSkipped(
swId, "Software.Version GetAllProperties "
"failed: " +
ec3.message()));
}
return;
}
const std::string* version = nullptr;
const std::string* swInvPurpose = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), propertiesList,
"Purpose", swInvPurpose, "Version", version);
if (!success) {
BMCWEB_LOG_ERROR << "Skipping " << swId
<< ": failed to unpack "
"Software.Version properties";
messages::appendToExtendedInfo(
aResp->res,
messages::subResourceSkipped(
swId,
"failed to unpack Software.Version "
"properties"));
return;
}
if (version == nullptr || version->empty()) {
BMCWEB_LOG_ERROR << "Skipping " << swId
<< ": missing or empty Version";
messages::appendToExtendedInfo(
aResp->res,
messages::subResourceSkipped(
swId,
"Software.Version Version property is "
"missing or empty"));
return;
}
if (swInvPurpose == nullptr ||
*swInvPurpose != swVersionPurpose) {
// Not purpose we're looking for
return;
}
BMCWEB_LOG_DEBUG << "Image ID: " << swId;
BMCWEB_LOG_DEBUG << "Running image: " << runningImage;
BMCWEB_LOG_DEBUG << "Image purpose: " << *swInvPurpose;
BMCWEB_LOG_DEBUG << "Association: "
<< swAssociationToString(swAssociation);
BMCWEB_LOG_DEBUG << "Resource ID: " << resourceId;
bool isTargetActiveImage = runningImage;
if (runningImage &&
swAssociation == SwAssociation::System &&
resourceId != "system") {
if (!resourceId.empty() && !swId.empty() &&
resourceId.back() != swId.back() &&
std::isdigit(
static_cast<unsigned char>(swId.back()))) {
isTargetActiveImage = false;
}
}
if (populateLinkToImages) {
nlohmann::json& softwareImageMembers =
aResp->res.jsonValue["Links"]["SoftwareImages"];
// Firmware images are at
// /redfish/v1/UpdateService/FirmwareInventory/<Id>
// e.g. .../FirmwareInventory/82d3ec86
nlohmann::json::object_t member;
member["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "UpdateService",
"FirmwareInventory", swId);
softwareImageMembers.push_back(std::move(member));
aResp->res
.jsonValue["Links"]["SoftwareImages@odata.count"] =
softwareImageMembers.size();
if (isTargetActiveImage) {
nlohmann::json::object_t runningMember;
runningMember["@odata.id"] =
crow::utility::urlFromPieces(
"redfish", "v1", "UpdateService",
"FirmwareInventory", swId);
// Create the link to the running image
aResp->res.jsonValue["Links"]["ActiveSoftwareImage"] =
std::move(runningMember);
}
}
if (!activeVersionPropName.empty() &&
isTargetActiveImage) {
aResp->res.jsonValue[activeVersionPropName] = *version;
}
});
}
});
});
}
/**
* @brief Translate input swState to Redfish state
*
* This function will return the corresponding Redfish state
*
* @param[i] swState The OpenBMC software state
*
* @return The corresponding Redfish state
*/
inline resource::State getRedfishSwState(const std::string& swState) {
if (swState == "xyz.openbmc_project.Software.Activation.Activations.Active") {
return resource::State::Enabled;
}
if (swState ==
"xyz.openbmc_project.Software.Activation."
"Activations.Activating") {
return resource::State::Updating;
}
if (swState ==
"xyz.openbmc_project.Software.Activation."
"Activations.StandbySpare") {
return resource::State::StandbySpare;
}
BMCWEB_LOG_DEBUG << "Default sw state " << swState << " to Disabled";
return resource::State::Disabled;
}
/**
* @brief Translate input swState to Redfish health state
*
* This function will return the corresponding Redfish health state
*
* @param[i] swState The OpenBMC software state
*
* @return The corresponding Redfish health state
*/
inline std::string getRedfishSwHealth(const std::string& swState) {
if ((swState ==
"xyz.openbmc_project.Software.Activation.Activations.Active") ||
(swState == "xyz.openbmc_project.Software.Activation.Activations."
"Activating") ||
(swState ==
"xyz.openbmc_project.Software.Activation.Activations.Ready")) {
return "OK";
}
BMCWEB_LOG_DEBUG << "Sw state " << swState << " to Warning";
return "Warning";
}
/**
* @brief Put status of input swId into json response
*
* This function will put the appropriate Redfish state of the input
* software id to ["Status"]["State"] within the json response
*
* @param[i,o] aResp Async response object
* @param[i] swId The software ID to get status for
* @param[i] dbusSvc The dbus service implementing the software object
*
* @return void
*/
inline void getSwStatus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::shared_ptr<std::string>& swId,
const std::string& dbusSvc) {
BMCWEB_LOG_DEBUG << "getSwStatus: swId " << *swId << " svc " << dbusSvc;
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getAllProperties(
dbusSvc, "/xyz/openbmc_project/software/" + *swId,
"xyz.openbmc_project.Software.Activation", context,
[asyncResp, swId](
const boost::system::error_code& errorCode,
const dbus::utility::DBusPropertiesMap& propertiesList) {
if (errorCode) {
// not all swtypes are updateable, this is ok
asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
return;
}
const std::string* swInvActivation = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), propertiesList, "Activation",
swInvActivation);
if (!success) {
messages::internalError(asyncResp->res);
return;
}
if (swInvActivation == nullptr) {
messages::internalError(asyncResp->res);
return;
}
BMCWEB_LOG_DEBUG << "getSwStatus: Activation " << *swInvActivation;
asyncResp->res.jsonValue["Status"]["State"] =
getRedfishSwState(*swInvActivation);
asyncResp->res.jsonValue["Status"]["Health"] =
getRedfishSwHealth(*swInvActivation);
});
}
/**
* @brief Updates programmable status of input swId into json response
*
* This function checks whether software inventory component
* can be programmable or not and fill's the "Updatable"
* Property.
*
* @param[i,o] asyncResp Async response object
* @param[i] swId The software ID
*/
inline void getSwUpdatableStatus(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::shared_ptr<std::string>& swId) {
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
dbus_utils::getAssociationEndPoints(
"/xyz/openbmc_project/software/updateable", requestContext,
[asyncResp, swId](const boost::system::error_code& ec,
const dbus::utility::MapperEndPoints& objPaths) {
if (ec) {
BMCWEB_LOG_DEBUG << " error_code = " << ec
<< " error msg = " << ec.message();
// System can exist with no updateable software,
// so don't throw error here.
return;
}
std::string reqSwObjPath = "/xyz/openbmc_project/software/" + *swId;
if (std::find(objPaths.begin(), objPaths.end(), reqSwObjPath) !=
objPaths.end()) {
asyncResp->res.jsonValue["Updateable"] = true;
return;
}
});
}
/**
* @brief Updates programmable settings of input swId into json response
*
* This function checks whether firmware inventory component's setting from
* D-bus. It only looks like the `WriteProtected` property to see if the
* firmware is write protect.
*
* @param[i,o] asyncResp Async response object
* @param[i] service Service handling the firmware
* @param[i] path D-bus path of the firmware component
*/
inline void getSoftwareSettings(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& service, const std::string& path) {
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
dbus_utils::getProperty<bool>(
service, path, "xyz.openbmc_project.Software.Settings", "WriteProtected",
requestContext,
[asyncResp](const boost::system::error_code ec, bool writeProtected) {
if (ec) {
// System can exist with no updateable/writable firmware,
// so don't throw error here.
BMCWEB_LOG_DEBUG << " getFirmwareSettings error_code = " << ec
<< " error msg = " << ec.message();
return;
}
asyncResp->res.jsonValue["WriteProtected"] = writeProtected;
});
}
} // namespace sw_util
} // namespace redfish
#endif // THIRD_PARTY_GBMCWEB_REDFISH_CORE_INCLUDE_UTILS_SW_UTILS_H_