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