1 #pragma once 2 #include "async_resp.hpp" 3 #include "dbus_utility.hpp" 4 #include "error_messages.hpp" 5 #include "generated/enums/resource.hpp" 6 #include "http/utility.hpp" 7 #include "utils/dbus_utils.hpp" 8 9 #include <boost/system/error_code.hpp> 10 #include <boost/url/format.hpp> 11 #include <sdbusplus/asio/property.hpp> 12 #include <sdbusplus/unpack_properties.hpp> 13 14 #include <algorithm> 15 #include <array> 16 #include <optional> 17 #include <ranges> 18 #include <string> 19 #include <string_view> 20 #include <vector> 21 22 namespace redfish 23 { 24 namespace sw_util 25 { 26 /* @brief String that indicates a bios software instance */ 27 constexpr const char* biosPurpose = 28 "xyz.openbmc_project.Software.Version.VersionPurpose.Host"; 29 30 /* @brief String that indicates a BMC software instance */ 31 constexpr const char* bmcPurpose = 32 "xyz.openbmc_project.Software.Version.VersionPurpose.BMC"; 33 34 inline std::optional<sdbusplus::message::object_path> 35 getFunctionalSoftwarePath(const std::string& swType) 36 { 37 if (swType == bmcPurpose) 38 { 39 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 40 { 41 return sdbusplus::message::object_path( 42 "/xyz/openbmc_project/software/bmc/functional"); 43 } 44 else 45 { 46 return sdbusplus::message::object_path( 47 "/xyz/openbmc_project/software/functional"); 48 } 49 } 50 else if (swType == biosPurpose) 51 { 52 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 53 { 54 return sdbusplus::message::object_path( 55 "/xyz/openbmc_project/software/bios/functional"); 56 } 57 else 58 { 59 return sdbusplus::message::object_path( 60 "/xyz/openbmc_project/software/functional"); 61 } 62 } 63 else 64 { 65 BMCWEB_LOG_ERROR("No valid software path"); 66 return std::nullopt; 67 } 68 } 69 70 /** 71 * @brief Populate the running software version and image links 72 * 73 * @param[i,o] asyncResp Async response object 74 * @param[i] swVersionPurpose Indicates what target to look for 75 * @param[i] activeVersionPropName Index in asyncResp->res.jsonValue to write 76 * the running software version to 77 * @param[i] populateLinkToImages Populate asyncResp->res "Links" 78 * "ActiveSoftwareImage" with a link to the running software image and 79 * "SoftwareImages" with a link to the all its software images 80 * 81 * @return void 82 */ 83 inline void populateSoftwareInformation( 84 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 85 const std::string& swVersionPurpose, 86 const std::string& activeVersionPropName, const bool populateLinkToImages) 87 { 88 auto swPath = getFunctionalSoftwarePath(swVersionPurpose); 89 if (!swPath) 90 { 91 BMCWEB_LOG_ERROR("Invalid software type"); 92 messages::internalError(asyncResp->res); 93 return; 94 } 95 // Used later to determine running (known on Redfish as active) Sw images 96 dbus::utility::getAssociationEndPoints( 97 swPath.value().str, 98 [asyncResp, swVersionPurpose, activeVersionPropName, 99 populateLinkToImages]( 100 const boost::system::error_code& ec, 101 const dbus::utility::MapperEndPoints& functionalSw) { 102 BMCWEB_LOG_DEBUG("populateSoftwareInformation enter"); 103 if (ec) 104 { 105 BMCWEB_LOG_ERROR("error_code = {}", ec); 106 BMCWEB_LOG_ERROR("error msg = {}", ec.message()); 107 messages::internalError(asyncResp->res); 108 return; 109 } 110 111 if (functionalSw.empty()) 112 { 113 // Could keep going and try to populate SoftwareImages but 114 // something is seriously wrong, so just fail 115 BMCWEB_LOG_ERROR("Zero functional software in system"); 116 messages::internalError(asyncResp->res); 117 return; 118 } 119 120 std::vector<std::string> functionalSwIds; 121 // example functionalSw: 122 // v as 2 "/xyz/openbmc_project/software/ace821ef" 123 // "/xyz/openbmc_project/software/230fb078" 124 for (const auto& sw : functionalSw) 125 { 126 sdbusplus::message::object_path path(sw); 127 std::string leaf = path.filename(); 128 if (leaf.empty()) 129 { 130 continue; 131 } 132 133 functionalSwIds.push_back(leaf); 134 } 135 136 constexpr std::array<std::string_view, 1> interfaces = { 137 "xyz.openbmc_project.Software.Version"}; 138 dbus::utility::getSubTree( 139 "/xyz/openbmc_project/software", 0, interfaces, 140 [asyncResp, swVersionPurpose, activeVersionPropName, 141 populateLinkToImages, functionalSwIds]( 142 const boost::system::error_code& ec2, 143 const dbus::utility::MapperGetSubTreeResponse& subtree) { 144 if (ec2) 145 { 146 BMCWEB_LOG_ERROR("error_code = {}", ec2); 147 BMCWEB_LOG_ERROR("error msg = {}", ec2.message()); 148 messages::internalError(asyncResp->res); 149 return; 150 } 151 152 BMCWEB_LOG_DEBUG("Found {} images", subtree.size()); 153 154 for (const std::pair<std::string, 155 std::vector<std::pair< 156 std::string, std::vector<std::string>>>>& 157 obj : subtree) 158 { 159 sdbusplus::message::object_path path(obj.first); 160 std::string swId = path.filename(); 161 if (swId.empty()) 162 { 163 messages::internalError(asyncResp->res); 164 BMCWEB_LOG_ERROR("Invalid software ID"); 165 166 return; 167 } 168 169 bool runningImage = false; 170 // Look at Ids from 171 // /xyz/openbmc_project/software/functional 172 // to determine if this is a running image 173 if (std::ranges::find(functionalSwIds, swId) != 174 functionalSwIds.end()) 175 { 176 runningImage = true; 177 } 178 179 // Now grab its version info 180 sdbusplus::asio::getAllProperties( 181 *crow::connections::systemBus, obj.second[0].first, 182 obj.first, "xyz.openbmc_project.Software.Version", 183 [asyncResp, swId, runningImage, swVersionPurpose, 184 activeVersionPropName, populateLinkToImages]( 185 const boost::system::error_code& ec3, 186 const dbus::utility::DBusPropertiesMap& 187 propertiesList) { 188 if (ec3) 189 { 190 BMCWEB_LOG_ERROR("error_code = {}", ec3); 191 BMCWEB_LOG_ERROR("error msg = {}", ec3.message()); 192 // Have seen the code update app delete the D-Bus 193 // object, during code update, between the call to 194 // mapper and here. Just leave these properties off if 195 // resource not found. 196 if (ec3.value() == EBADR) 197 { 198 return; 199 } 200 messages::internalError(asyncResp->res); 201 return; 202 } 203 // example propertiesList 204 // a{sv} 2 "Version" s 205 // "IBM-witherspoon-OP9-v2.0.10-2.22" "Purpose" 206 // s 207 // "xyz.openbmc_project.Software.Version.VersionPurpose.Host" 208 const std::string* version = nullptr; 209 const std::string* swInvPurpose = nullptr; 210 211 const bool success = sdbusplus::unpackPropertiesNoThrow( 212 dbus_utils::UnpackErrorPrinter(), propertiesList, 213 "Purpose", swInvPurpose, "Version", version); 214 215 if (!success) 216 { 217 messages::internalError(asyncResp->res); 218 return; 219 } 220 221 if (version == nullptr || version->empty()) 222 { 223 messages::internalError(asyncResp->res); 224 return; 225 } 226 if (swInvPurpose == nullptr || 227 *swInvPurpose != swVersionPurpose) 228 { 229 // Not purpose we're looking for 230 return; 231 } 232 233 BMCWEB_LOG_DEBUG("Image ID: {}", swId); 234 BMCWEB_LOG_DEBUG("Running image: {}", runningImage); 235 BMCWEB_LOG_DEBUG("Image purpose: {}", *swInvPurpose); 236 237 if (populateLinkToImages) 238 { 239 nlohmann::json& softwareImageMembers = 240 asyncResp->res.jsonValue["Links"]["SoftwareImages"]; 241 // Firmware images are at 242 // /redfish/v1/UpdateService/FirmwareInventory/<Id> 243 // e.g. .../FirmwareInventory/82d3ec86 244 nlohmann::json::object_t member; 245 member["@odata.id"] = boost::urls::format( 246 "/redfish/v1/UpdateService/FirmwareInventory/{}", 247 swId); 248 softwareImageMembers.emplace_back(std::move(member)); 249 asyncResp->res 250 .jsonValue["Links"]["SoftwareImages@odata.count"] = 251 softwareImageMembers.size(); 252 253 if (runningImage) 254 { 255 nlohmann::json::object_t runningMember; 256 runningMember["@odata.id"] = boost::urls::format( 257 "/redfish/v1/UpdateService/FirmwareInventory/{}", 258 swId); 259 // Create the link to the running image 260 asyncResp->res 261 .jsonValue["Links"]["ActiveSoftwareImage"] = 262 std::move(runningMember); 263 } 264 } 265 if (!activeVersionPropName.empty() && runningImage) 266 { 267 asyncResp->res.jsonValue[activeVersionPropName] = 268 *version; 269 } 270 }); 271 } 272 }); 273 }); 274 } 275 276 /** 277 * @brief Translate input swState to Redfish state 278 * 279 * This function will return the corresponding Redfish state 280 * 281 * @param[i] swState The OpenBMC software state 282 * 283 * @return The corresponding Redfish state 284 */ 285 inline resource::State getRedfishSwState(const std::string& swState) 286 { 287 if (swState == "xyz.openbmc_project.Software.Activation.Activations.Active") 288 { 289 return resource::State::Enabled; 290 } 291 if (swState == "xyz.openbmc_project.Software.Activation." 292 "Activations.Activating") 293 { 294 return resource::State::Updating; 295 } 296 if (swState == "xyz.openbmc_project.Software.Activation." 297 "Activations.StandbySpare") 298 { 299 return resource::State::StandbySpare; 300 } 301 BMCWEB_LOG_DEBUG("Default sw state {} to Disabled", swState); 302 return resource::State::Disabled; 303 } 304 305 /** 306 * @brief Translate input swState to Redfish health state 307 * 308 * This function will return the corresponding Redfish health state 309 * 310 * @param[i] swState The OpenBMC software state 311 * 312 * @return The corresponding Redfish health state 313 */ 314 inline std::string getRedfishSwHealth(const std::string& swState) 315 { 316 if ((swState == 317 "xyz.openbmc_project.Software.Activation.Activations.Active") || 318 (swState == "xyz.openbmc_project.Software.Activation.Activations." 319 "Activating") || 320 (swState == 321 "xyz.openbmc_project.Software.Activation.Activations.Ready")) 322 { 323 return "OK"; 324 } 325 BMCWEB_LOG_DEBUG("Sw state {} to Warning", swState); 326 return "Warning"; 327 } 328 329 /** 330 * @brief Put status of input swId into json response 331 * 332 * This function will put the appropriate Redfish state of the input 333 * software id to ["Status"]["State"] within the json response 334 * 335 * @param[i,o] asyncResp Async response object 336 * @param[i] swId The software ID to get status for 337 * @param[i] dbusSvc The dbus service implementing the software object 338 * 339 * @return void 340 */ 341 inline void getSwStatus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 342 const std::shared_ptr<std::string>& swId, 343 const std::string& dbusSvc) 344 { 345 BMCWEB_LOG_DEBUG("getSwStatus: swId {} svc {}", *swId, dbusSvc); 346 347 sdbusplus::asio::getAllProperties( 348 *crow::connections::systemBus, dbusSvc, 349 "/xyz/openbmc_project/software/" + *swId, 350 "xyz.openbmc_project.Software.Activation", 351 [asyncResp, 352 swId](const boost::system::error_code& ec, 353 const dbus::utility::DBusPropertiesMap& propertiesList) { 354 if (ec) 355 { 356 // not all swtypes are updateable, this is ok 357 asyncResp->res.jsonValue["Status"]["State"] = 358 resource::State::Enabled; 359 return; 360 } 361 362 const std::string* swInvActivation = nullptr; 363 364 const bool success = sdbusplus::unpackPropertiesNoThrow( 365 dbus_utils::UnpackErrorPrinter(), propertiesList, "Activation", 366 swInvActivation); 367 368 if (!success) 369 { 370 messages::internalError(asyncResp->res); 371 return; 372 } 373 374 if (swInvActivation == nullptr) 375 { 376 messages::internalError(asyncResp->res); 377 return; 378 } 379 380 BMCWEB_LOG_DEBUG("getSwStatus: Activation {}", *swInvActivation); 381 asyncResp->res.jsonValue["Status"]["State"] = 382 getRedfishSwState(*swInvActivation); 383 asyncResp->res.jsonValue["Status"]["Health"] = 384 getRedfishSwHealth(*swInvActivation); 385 }); 386 } 387 388 inline void handleUpdateableEndpoints( 389 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 390 const std::shared_ptr<std::string>& swId, 391 const boost::system::error_code& ec, 392 const dbus::utility::MapperEndPoints& objPaths) 393 { 394 if (ec) 395 { 396 BMCWEB_LOG_DEBUG(" error_code = {} error msg = {}", ec, ec.message()); 397 // System can exist with no updateable software, 398 // so don't throw error here. 399 return; 400 } 401 sdbusplus::message::object_path reqSwObjPath( 402 "/xyz/openbmc_project/software"); 403 reqSwObjPath = reqSwObjPath / *swId; 404 405 if (std::ranges::find(objPaths, reqSwObjPath.str) != objPaths.end()) 406 { 407 asyncResp->res.jsonValue["Updateable"] = true; 408 return; 409 } 410 } 411 412 inline void 413 handleUpdateableObject(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 414 const boost::system::error_code& ec, 415 const dbus::utility::MapperGetObject& objectInfo) 416 { 417 if (ec) 418 { 419 BMCWEB_LOG_DEBUG(" error_code = {} error msg = {}", ec, ec.message()); 420 // System can exist with no updateable software, 421 // so don't throw error here. 422 return; 423 } 424 if (objectInfo.empty()) 425 { 426 BMCWEB_LOG_DEBUG("No updateable software found"); 427 // System can exist with no updateable software, 428 // so don't throw error here. 429 return; 430 } 431 asyncResp->res.jsonValue["Updateable"] = true; 432 } 433 434 /** 435 * @brief Updates programmable status of input swId into json response 436 * 437 * This function checks whether software inventory component 438 * can be programmable or not and fill's the "Updatable" 439 * Property. 440 * 441 * @param[i,o] asyncResp Async response object 442 * @param[i] swId The software ID 443 */ 444 inline void 445 getSwUpdatableStatus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 446 const std::shared_ptr<std::string>& swId) 447 { 448 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 449 { 450 sdbusplus::message::object_path swObjectPath( 451 "/xyz/openbmc_project/software"); 452 swObjectPath = swObjectPath / *swId; 453 constexpr std::array<std::string_view, 1> interfaces = { 454 "xyz.openbmc_project.Software.Update"}; 455 dbus::utility::getDbusObject( 456 swObjectPath.str, interfaces, 457 std::bind_front(handleUpdateableObject, asyncResp)); 458 } 459 else 460 { 461 dbus::utility::getAssociationEndPoints( 462 "/xyz/openbmc_project/software/updateable", 463 std::bind_front(handleUpdateableEndpoints, asyncResp, swId)); 464 } 465 } 466 467 } // namespace sw_util 468 } // namespace redfish 469