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