blob: 3f0e38f8e15e6fe58cf9fc97716de3cf37826c53 [file] [log] [blame]
#pragma once
#include "async_resp.hpp"
#include "dbus_utility.hpp"
#include "error_messages.hpp"
#include "generated/enums/resource.hpp"
#include "http/utility.hpp"
#include "managed_store_types.hpp"
#include "utils/dbus_utils.hpp"
#include <boost/system/error_code.hpp>
#include <sdbusplus/asio/property.hpp>
#include <sdbusplus/unpack_properties.hpp>
#include <algorithm>
#include <array>
#include <string>
#include <string_view>
#include <vector>
#include "managed_store.hpp"
#ifdef UNIT_TEST_BUILD
#include "test/g3/mock_managed_store.hpp" // NOLINT
#endif
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";
/**
* @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
*
* @return void
*/
inline void
populateSoftwareInformation(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::string& swVersionPurpose,
const std::string& activeVersionPropName,
const bool populateLinkToImages)
{
// 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](
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](
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())
{
messages::internalError(aResp->res);
BMCWEB_LOG_ERROR << "Invalid software ID";
return;
}
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](
const boost::system::error_code& ec3,
const dbus::utility::DBusPropertiesMap&
propertiesList) {
if (ec3)
{
BMCWEB_LOG_ERROR << "error_code = " << ec3;
BMCWEB_LOG_ERROR << "error msg = " << ec3.message();
// Have seen the code update app delete the D-Bus
// object, during code update, between the call to
// mapper and here. Just leave these properties off if
// resource not found.
if (ec3.value() == EBADR)
{
return;
}
messages::internalError(aResp->res);
return;
}
// example propertiesList
// a{sv} 2 "Version" s
// "IBM-witherspoon-OP9-v2.0.10-2.22" "Purpose"
// s
// "xyz.openbmc_project.Software.Version.VersionPurpose.Host"
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)
{
messages::internalError(aResp->res);
return;
}
if (version == nullptr || version->empty())
{
messages::internalError(aResp->res);
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;
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 (runningImage)
{
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() && runningImage)
{
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