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) 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 if (version->empty()) 164 { 165 BMCWEB_LOG_INFO("Version is empty for swId: {}", swId); 166 return; 167 } 168 asyncResp->res.jsonValue[activeVersionPropName] = *version; 169 } 170 } 171 172 inline void afterGetSubtree( 173 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 174 const std::string& swVersionPurpose, 175 const std::string& activeVersionPropName, bool populateLinkToImages, 176 const std::vector<std::string>& functionalSwIds, 177 const boost::system::error_code& ec, 178 const dbus::utility::MapperGetSubTreeResponse& subtree) 179 { 180 if (ec) 181 { 182 if (ec.value() != EBADR) 183 { 184 BMCWEB_LOG_ERROR("error_code = {}", ec); 185 messages::internalError(asyncResp->res); 186 } 187 return; 188 } 189 190 BMCWEB_LOG_DEBUG("Found {} images", subtree.size()); 191 192 for (const std::pair< 193 std::string, 194 std::vector<std::pair<std::string, std::vector<std::string>>>>& 195 obj : subtree) 196 { 197 sdbusplus::message::object_path path(obj.first); 198 std::string swId = path.filename(); 199 if (swId.empty()) 200 { 201 messages::internalError(asyncResp->res); 202 BMCWEB_LOG_ERROR("Invalid software ID"); 203 204 return; 205 } 206 207 bool runningImage = false; 208 // Look at Ids from 209 // /xyz/openbmc_project/software/functional 210 // to determine if this is 211 // a running image 212 if (std::ranges::find(functionalSwIds, swId) != functionalSwIds.end()) 213 { 214 runningImage = true; 215 } 216 217 // Now grab its version 218 // info 219 dbus::utility::getAllProperties( 220 *crow::connections::systemBus, obj.second[0].first, obj.first, 221 "xyz.openbmc_project.Software.Version", 222 [asyncResp, swId, runningImage, swVersionPurpose, 223 activeVersionPropName, populateLinkToImages]( 224 const boost::system::error_code& ec3, 225 const dbus::utility::DBusPropertiesMap& propertiesList) { 226 afterGetProperties(asyncResp, swId, runningImage, 227 swVersionPurpose, activeVersionPropName, 228 populateLinkToImages, ec3, propertiesList); 229 }); 230 } 231 } 232 233 inline void afterAssociatedEndpoints( 234 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 235 const std::string& swVersionPurpose, 236 const std::string& activeVersionPropName, bool populateLinkToImages, 237 const boost::system::error_code& ec, 238 const dbus::utility::MapperEndPoints& functionalSw) 239 { 240 BMCWEB_LOG_DEBUG("populateSoftwareInformation enter"); 241 if (ec) 242 { 243 BMCWEB_LOG_DEBUG("error_code = {}", ec); 244 // No functional software for this swVersionPurpose, so just 245 return; 246 } 247 248 if (functionalSw.empty()) 249 { 250 // Could keep going and try to populate SoftwareImages 251 // but something is seriously wrong, so just fail 252 BMCWEB_LOG_ERROR("Zero functional software in system"); 253 messages::internalError(asyncResp->res); 254 return; 255 } 256 257 std::vector<std::string> functionalSwIds; 258 // example functionalSw: 259 // v as 2 "/xyz/openbmc_project/software/ace821ef" 260 // "/xyz/openbmc_project/software/230fb078" 261 for (const auto& sw : functionalSw) 262 { 263 sdbusplus::message::object_path path(sw); 264 std::string leaf = path.filename(); 265 if (leaf.empty()) 266 { 267 continue; 268 } 269 270 functionalSwIds.push_back(leaf); 271 } 272 273 constexpr std::array<std::string_view, 1> interfaces = { 274 "xyz.openbmc_project.Software.Version"}; 275 dbus::utility::getSubTree( 276 "/xyz/openbmc_project/software", 0, interfaces, 277 [asyncResp, swVersionPurpose, activeVersionPropName, 278 populateLinkToImages, functionalSwIds]( 279 const boost::system::error_code& ec2, 280 const dbus::utility::MapperGetSubTreeResponse& subtree) { 281 afterGetSubtree(asyncResp, swVersionPurpose, activeVersionPropName, 282 populateLinkToImages, functionalSwIds, ec2, 283 subtree); 284 }); 285 } 286 287 /** 288 * @brief Populate the running software version and image links 289 * 290 * @param[i,o] asyncResp Async response object 291 * @param[i] swVersionPurpose Indicates what target to look for 292 * @param[i] activeVersionPropName Index in asyncResp->res.jsonValue to write 293 * the running software version to 294 * @param[i] populateLinkToImages Populate asyncResp->res "Links" 295 * "ActiveSoftwareImage" with a link to the running software image and 296 * "SoftwareImages" with a link to the all its software images 297 * 298 * @return void 299 */ 300 inline void populateSoftwareInformation( 301 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 302 const std::string& swVersionPurpose, 303 const std::string& activeVersionPropName, const bool populateLinkToImages) 304 { 305 auto swPath = getFunctionalSoftwarePath(swVersionPurpose); 306 if (!swPath) 307 { 308 BMCWEB_LOG_ERROR("Invalid software type"); 309 messages::internalError(asyncResp->res); 310 return; 311 } 312 // Used later to determine running (known on Redfish as active) Sw images 313 dbus::utility::getAssociationEndPoints( 314 swPath.value().str, 315 [asyncResp, swVersionPurpose, activeVersionPropName, 316 populateLinkToImages]( 317 const boost::system::error_code& ec, 318 const dbus::utility::MapperEndPoints& functionalSw) { 319 afterAssociatedEndpoints(asyncResp, swVersionPurpose, 320 activeVersionPropName, 321 populateLinkToImages, ec, functionalSw); 322 }); 323 } 324 325 /** 326 * @brief Translate input swState to Redfish state 327 * 328 * This function will return the corresponding Redfish state 329 * 330 * @param[i] swState The OpenBMC software state 331 * 332 * @return The corresponding Redfish state 333 */ 334 inline resource::State getRedfishSwState(const std::string& swState) 335 { 336 if (swState == "xyz.openbmc_project.Software.Activation.Activations.Active") 337 { 338 return resource::State::Enabled; 339 } 340 if (swState == "xyz.openbmc_project.Software.Activation." 341 "Activations.Activating") 342 { 343 return resource::State::Updating; 344 } 345 if (swState == "xyz.openbmc_project.Software.Activation." 346 "Activations.StandbySpare") 347 { 348 return resource::State::StandbySpare; 349 } 350 BMCWEB_LOG_DEBUG("Default sw state {} to Disabled", swState); 351 return resource::State::Disabled; 352 } 353 354 /** 355 * @brief Translate input swState to Redfish health state 356 * 357 * This function will return the corresponding Redfish health state 358 * 359 * @param[i] swState The OpenBMC software state 360 * 361 * @return The corresponding Redfish health state 362 */ 363 inline std::string getRedfishSwHealth(const std::string& swState) 364 { 365 if ((swState == 366 "xyz.openbmc_project.Software.Activation.Activations.Active") || 367 (swState == "xyz.openbmc_project.Software.Activation.Activations." 368 "Activating") || 369 (swState == 370 "xyz.openbmc_project.Software.Activation.Activations.Ready")) 371 { 372 return "OK"; 373 } 374 BMCWEB_LOG_DEBUG("Sw state {} to Warning", swState); 375 return "Warning"; 376 } 377 378 /** 379 * @brief Put LowestSupportedVersion of input swId into json response 380 * 381 * This function will put the MinimumVersion from D-Bus of the input 382 * software id to ["LowestSupportedVersion"]. 383 * 384 * @param[i,o] asyncResp Async response object 385 * @param[i] swId The software ID to get Minimum Version for 386 * @param[i] dbusSvc The dbus service implementing the software object 387 * 388 * @return void 389 */ 390 inline void getSwMinimumVersion( 391 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 392 const std::shared_ptr<std::string>& swId, const std::string& dbusSvc) 393 { 394 BMCWEB_LOG_DEBUG("getSwMinimumVersion: svc {}, swId {}", dbusSvc, *swId); 395 396 sdbusplus::message::object_path path("/xyz/openbmc_project/software"); 397 path /= *swId; 398 399 dbus::utility::getProperty<std::string>( 400 dbusSvc, path, "xyz.openbmc_project.Software.MinimumVersion", 401 "MinimumVersion", 402 [asyncResp](const boost::system::error_code& ec, 403 const std::string& swMinimumVersion) { 404 if (ec) 405 { 406 // not all software has this interface and it is not critical 407 return; 408 } 409 410 BMCWEB_LOG_DEBUG("getSwMinimumVersion: MinimumVersion {}", 411 swMinimumVersion); 412 413 asyncResp->res.jsonValue["LowestSupportedVersion"] = 414 swMinimumVersion; 415 }); 416 } 417 418 /** 419 * @brief Put status of input swId into json response 420 * 421 * This function will put the appropriate Redfish state of the input 422 * software id to ["Status"]["State"] within the json response 423 * 424 * @param[i,o] asyncResp Async response object 425 * @param[i] swId The software ID to get status for 426 * @param[i] dbusSvc The dbus service implementing the software object 427 * 428 * @return void 429 */ 430 inline void getSwStatus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 431 const std::shared_ptr<std::string>& swId, 432 const std::string& dbusSvc) 433 { 434 BMCWEB_LOG_DEBUG("getSwStatus: swId {} svc {}", *swId, dbusSvc); 435 436 dbus::utility::getAllProperties( 437 dbusSvc, "/xyz/openbmc_project/software/" + *swId, 438 "xyz.openbmc_project.Software.Activation", 439 [asyncResp, 440 swId](const boost::system::error_code& ec, 441 const dbus::utility::DBusPropertiesMap& propertiesList) { 442 if (ec) 443 { 444 // not all swtypes are updateable, this is ok 445 asyncResp->res.jsonValue["Status"]["State"] = 446 resource::State::Enabled; 447 return; 448 } 449 450 const std::string* swInvActivation = nullptr; 451 452 const bool success = sdbusplus::unpackPropertiesNoThrow( 453 dbus_utils::UnpackErrorPrinter(), propertiesList, "Activation", 454 swInvActivation); 455 456 if (!success) 457 { 458 messages::internalError(asyncResp->res); 459 return; 460 } 461 462 if (swInvActivation == nullptr) 463 { 464 messages::internalError(asyncResp->res); 465 return; 466 } 467 468 BMCWEB_LOG_DEBUG("getSwStatus: Activation {}", *swInvActivation); 469 asyncResp->res.jsonValue["Status"]["State"] = 470 getRedfishSwState(*swInvActivation); 471 asyncResp->res.jsonValue["Status"]["Health"] = 472 getRedfishSwHealth(*swInvActivation); 473 }); 474 } 475 476 inline void handleUpdateableEndpoints( 477 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 478 const std::shared_ptr<std::string>& swId, 479 const boost::system::error_code& ec, 480 const dbus::utility::MapperEndPoints& objPaths) 481 { 482 if (ec) 483 { 484 BMCWEB_LOG_DEBUG(" error_code = {} error msg = {}", ec, ec.message()); 485 // System can exist with no updateable software, 486 // so don't throw error here. 487 return; 488 } 489 sdbusplus::message::object_path reqSwObjPath( 490 "/xyz/openbmc_project/software"); 491 reqSwObjPath = reqSwObjPath / *swId; 492 493 if (std::ranges::find(objPaths, reqSwObjPath.str) != objPaths.end()) 494 { 495 asyncResp->res.jsonValue["Updateable"] = true; 496 return; 497 } 498 } 499 500 inline void handleUpdateableObject( 501 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 502 const boost::system::error_code& ec, 503 const dbus::utility::MapperGetObject& objectInfo) 504 { 505 if (ec) 506 { 507 BMCWEB_LOG_DEBUG(" error_code = {} error msg = {}", ec, ec.message()); 508 // System can exist with no updateable software, 509 // so don't throw error here. 510 return; 511 } 512 if (objectInfo.empty()) 513 { 514 BMCWEB_LOG_DEBUG("No updateable software found"); 515 // System can exist with no updateable software, 516 // so don't throw error here. 517 return; 518 } 519 asyncResp->res.jsonValue["Updateable"] = true; 520 } 521 522 /** 523 * @brief Updates programmable status of input swId into json response 524 * 525 * This function checks whether software inventory component 526 * can be programmable or not and fill's the "Updatable" 527 * Property. 528 * 529 * @param[i,o] asyncResp Async response object 530 * @param[i] swId The software ID 531 */ 532 inline void getSwUpdatableStatus( 533 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 534 const std::shared_ptr<std::string>& swId) 535 { 536 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 537 { 538 sdbusplus::message::object_path swObjectPath( 539 "/xyz/openbmc_project/software"); 540 swObjectPath = swObjectPath / *swId; 541 constexpr std::array<std::string_view, 1> interfaces = { 542 "xyz.openbmc_project.Software.Update"}; 543 dbus::utility::getDbusObject( 544 swObjectPath.str, interfaces, 545 std::bind_front(handleUpdateableObject, asyncResp)); 546 } 547 else 548 { 549 dbus::utility::getAssociationEndPoints( 550 "/xyz/openbmc_project/software/updateable", 551 std::bind_front(handleUpdateableEndpoints, asyncResp, swId)); 552 } 553 } 554 555 } // namespace sw_util 556 } // namespace redfish 557