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 "dbus_singleton.hpp" 8 #include "dbus_utility.hpp" 9 #include "error_messages.hpp" 10 #include "generated/enums/resource.hpp" 11 #include "logging.hpp" 12 #include "utils/dbus_utils.hpp" 13 14 #include <asm-generic/errno.h> 15 16 #include <boost/system/error_code.hpp> 17 #include <boost/url/format.hpp> 18 #include <nlohmann/json.hpp> 19 #include <sdbusplus/message/native_types.hpp> 20 #include <sdbusplus/unpack_properties.hpp> 21 22 #include <algorithm> 23 #include <array> 24 #include <functional> 25 #include <memory> 26 #include <optional> 27 #include <ranges> 28 #include <string> 29 #include <string_view> 30 #include <utility> 31 #include <vector> 32 33 namespace redfish 34 { 35 namespace sw_util 36 { 37 /* @brief String that indicates a bios software instance */ 38 constexpr const char* biosPurpose = 39 "xyz.openbmc_project.Software.Version.VersionPurpose.Host"; 40 41 /* @brief String that indicates a BMC software instance */ 42 constexpr const char* bmcPurpose = 43 "xyz.openbmc_project.Software.Version.VersionPurpose.BMC"; 44 45 inline std::optional<sdbusplus::message::object_path> 46 getFunctionalSoftwarePath(const std::string& swType) 47 { 48 if (swType == bmcPurpose) 49 { 50 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 51 { 52 return sdbusplus::message::object_path( 53 "/xyz/openbmc_project/software/bmc/functional"); 54 } 55 else 56 { 57 return sdbusplus::message::object_path( 58 "/xyz/openbmc_project/software/functional"); 59 } 60 } 61 else if (swType == biosPurpose) 62 { 63 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 64 { 65 return sdbusplus::message::object_path( 66 "/xyz/openbmc_project/software/bios/functional"); 67 } 68 else 69 { 70 return sdbusplus::message::object_path( 71 "/xyz/openbmc_project/software/functional"); 72 } 73 } 74 else 75 { 76 BMCWEB_LOG_ERROR("No valid software path"); 77 return std::nullopt; 78 } 79 } 80 81 inline void afterGetProperties( 82 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 83 const std::string& swId, bool runningImage, 84 const std::string& swVersionPurpose, 85 const std::string& activeVersionPropName, bool populateLinkToImages, 86 const boost::system::error_code& ec, 87 const dbus::utility::DBusPropertiesMap& propertiesList) 88 { 89 if (ec) 90 { 91 BMCWEB_LOG_ERROR("error_code = {}", ec); 92 BMCWEB_LOG_ERROR("error msg = {}", ec.message()); 93 // Have seen the code update app delete the 94 // D-Bus object, during code update, between 95 // the call to mapper and here. Just leave 96 // these properties off if resource not 97 // found. 98 if (ec.value() == EBADR) 99 { 100 return; 101 } 102 messages::internalError(asyncResp->res); 103 return; 104 } 105 // example propertiesList 106 // a{sv} 2 "Version" s 107 // "IBM-witherspoon-OP9-v2.0.10-2.22" "Purpose" 108 // s 109 // "xyz.openbmc_project.Software.Version.VersionPurpose.Host" 110 const std::string* version = nullptr; 111 const std::string* swInvPurpose = nullptr; 112 113 const bool success = sdbusplus::unpackPropertiesNoThrow( 114 dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose", 115 swInvPurpose, "Version", version); 116 117 if (!success) 118 { 119 messages::internalError(asyncResp->res); 120 return; 121 } 122 123 if (version == nullptr || version->empty()) 124 { 125 messages::internalError(asyncResp->res); 126 return; 127 } 128 if (swInvPurpose == nullptr || *swInvPurpose != swVersionPurpose) 129 { 130 // Not purpose we're looking for 131 return; 132 } 133 134 BMCWEB_LOG_DEBUG("Image ID: {}", swId); 135 BMCWEB_LOG_DEBUG("Running image: {}", runningImage); 136 BMCWEB_LOG_DEBUG("Image purpose: {}", *swInvPurpose); 137 138 if (populateLinkToImages) 139 { 140 nlohmann::json& softwareImageMembers = 141 asyncResp->res.jsonValue["Links"]["SoftwareImages"]; 142 // Firmware images are at 143 // /redfish/v1/UpdateService/FirmwareInventory/<Id> 144 // e.g. .../FirmwareInventory/82d3ec86 145 nlohmann::json::object_t member; 146 member["@odata.id"] = boost::urls::format( 147 "/redfish/v1/UpdateService/FirmwareInventory/{}", swId); 148 softwareImageMembers.emplace_back(std::move(member)); 149 asyncResp->res.jsonValue["Links"]["SoftwareImages@odata.count"] = 150 softwareImageMembers.size(); 151 152 if (runningImage) 153 { 154 nlohmann::json::object_t runningMember; 155 runningMember["@odata.id"] = boost::urls::format( 156 "/redfish/v1/UpdateService/FirmwareInventory/{}", swId); 157 // Create the link to the running image 158 asyncResp->res.jsonValue["Links"]["ActiveSoftwareImage"] = 159 std::move(runningMember); 160 } 161 } 162 if (!activeVersionPropName.empty() && runningImage) 163 { 164 asyncResp->res.jsonValue[activeVersionPropName] = *version; 165 } 166 } 167 168 inline void afterGetSubtree( 169 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 170 const std::string& swVersionPurpose, 171 const std::string& activeVersionPropName, bool populateLinkToImages, 172 const std::vector<std::string>& functionalSwIds, 173 const boost::system::error_code& ec, 174 const dbus::utility::MapperGetSubTreeResponse& subtree) 175 { 176 if (ec) 177 { 178 BMCWEB_LOG_ERROR("error_code = {}", ec); 179 BMCWEB_LOG_ERROR("error msg = {}", ec.message()); 180 messages::internalError(asyncResp->res); 181 return; 182 } 183 184 BMCWEB_LOG_DEBUG("Found {} images", subtree.size()); 185 186 for (const std::pair< 187 std::string, 188 std::vector<std::pair<std::string, std::vector<std::string>>>>& 189 obj : subtree) 190 { 191 sdbusplus::message::object_path path(obj.first); 192 std::string swId = path.filename(); 193 if (swId.empty()) 194 { 195 messages::internalError(asyncResp->res); 196 BMCWEB_LOG_ERROR("Invalid software ID"); 197 198 return; 199 } 200 201 bool runningImage = false; 202 // Look at Ids from 203 // /xyz/openbmc_project/software/functional 204 // to determine if this is 205 // a running image 206 if (std::ranges::find(functionalSwIds, swId) != functionalSwIds.end()) 207 { 208 runningImage = true; 209 } 210 211 // Now grab its version 212 // info 213 dbus::utility::getAllProperties( 214 *crow::connections::systemBus, obj.second[0].first, obj.first, 215 "xyz.openbmc_project.Software.Version", 216 [asyncResp, swId, runningImage, swVersionPurpose, 217 activeVersionPropName, populateLinkToImages]( 218 const boost::system::error_code& ec3, 219 const dbus::utility::DBusPropertiesMap& propertiesList) { 220 afterGetProperties(asyncResp, swId, runningImage, 221 swVersionPurpose, activeVersionPropName, 222 populateLinkToImages, ec3, propertiesList); 223 }); 224 } 225 } 226 227 inline void afterAssociatedEndpoints( 228 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 229 const std::string& swVersionPurpose, 230 const std::string& activeVersionPropName, bool populateLinkToImages, 231 const boost::system::error_code& ec, 232 const dbus::utility::MapperEndPoints& functionalSw) 233 { 234 BMCWEB_LOG_DEBUG("populateSoftwareInformation enter"); 235 if (ec) 236 { 237 BMCWEB_LOG_DEBUG("error_code = {}", ec); 238 BMCWEB_LOG_DEBUG("error msg = {}", ec.message()); 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 status of input swId into json response 375 * 376 * This function will put the appropriate Redfish state of the input 377 * software id to ["Status"]["State"] within the json response 378 * 379 * @param[i,o] asyncResp Async response object 380 * @param[i] swId The software ID to get status for 381 * @param[i] dbusSvc The dbus service implementing the software object 382 * 383 * @return void 384 */ 385 inline void getSwStatus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 386 const std::shared_ptr<std::string>& swId, 387 const std::string& dbusSvc) 388 { 389 BMCWEB_LOG_DEBUG("getSwStatus: swId {} svc {}", *swId, dbusSvc); 390 391 dbus::utility::getAllProperties( 392 dbusSvc, "/xyz/openbmc_project/software/" + *swId, 393 "xyz.openbmc_project.Software.Activation", 394 [asyncResp, 395 swId](const boost::system::error_code& ec, 396 const dbus::utility::DBusPropertiesMap& propertiesList) { 397 if (ec) 398 { 399 // not all swtypes are updateable, this is ok 400 asyncResp->res.jsonValue["Status"]["State"] = 401 resource::State::Enabled; 402 return; 403 } 404 405 const std::string* swInvActivation = nullptr; 406 407 const bool success = sdbusplus::unpackPropertiesNoThrow( 408 dbus_utils::UnpackErrorPrinter(), propertiesList, "Activation", 409 swInvActivation); 410 411 if (!success) 412 { 413 messages::internalError(asyncResp->res); 414 return; 415 } 416 417 if (swInvActivation == nullptr) 418 { 419 messages::internalError(asyncResp->res); 420 return; 421 } 422 423 BMCWEB_LOG_DEBUG("getSwStatus: Activation {}", *swInvActivation); 424 asyncResp->res.jsonValue["Status"]["State"] = 425 getRedfishSwState(*swInvActivation); 426 asyncResp->res.jsonValue["Status"]["Health"] = 427 getRedfishSwHealth(*swInvActivation); 428 }); 429 } 430 431 inline void handleUpdateableEndpoints( 432 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 433 const std::shared_ptr<std::string>& swId, 434 const boost::system::error_code& ec, 435 const dbus::utility::MapperEndPoints& objPaths) 436 { 437 if (ec) 438 { 439 BMCWEB_LOG_DEBUG(" error_code = {} error msg = {}", ec, ec.message()); 440 // System can exist with no updateable software, 441 // so don't throw error here. 442 return; 443 } 444 sdbusplus::message::object_path reqSwObjPath( 445 "/xyz/openbmc_project/software"); 446 reqSwObjPath = reqSwObjPath / *swId; 447 448 if (std::ranges::find(objPaths, reqSwObjPath.str) != objPaths.end()) 449 { 450 asyncResp->res.jsonValue["Updateable"] = true; 451 return; 452 } 453 } 454 455 inline void 456 handleUpdateableObject(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 457 const boost::system::error_code& ec, 458 const dbus::utility::MapperGetObject& objectInfo) 459 { 460 if (ec) 461 { 462 BMCWEB_LOG_DEBUG(" error_code = {} error msg = {}", ec, ec.message()); 463 // System can exist with no updateable software, 464 // so don't throw error here. 465 return; 466 } 467 if (objectInfo.empty()) 468 { 469 BMCWEB_LOG_DEBUG("No updateable software found"); 470 // System can exist with no updateable software, 471 // so don't throw error here. 472 return; 473 } 474 asyncResp->res.jsonValue["Updateable"] = true; 475 } 476 477 /** 478 * @brief Updates programmable status of input swId into json response 479 * 480 * This function checks whether software inventory component 481 * can be programmable or not and fill's the "Updatable" 482 * Property. 483 * 484 * @param[i,o] asyncResp Async response object 485 * @param[i] swId The software ID 486 */ 487 inline void 488 getSwUpdatableStatus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 489 const std::shared_ptr<std::string>& swId) 490 { 491 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 492 { 493 sdbusplus::message::object_path swObjectPath( 494 "/xyz/openbmc_project/software"); 495 swObjectPath = swObjectPath / *swId; 496 constexpr std::array<std::string_view, 1> interfaces = { 497 "xyz.openbmc_project.Software.Update"}; 498 dbus::utility::getDbusObject( 499 swObjectPath.str, interfaces, 500 std::bind_front(handleUpdateableObject, asyncResp)); 501 } 502 else 503 { 504 dbus::utility::getAssociationEndPoints( 505 "/xyz/openbmc_project/software/updateable", 506 std::bind_front(handleUpdateableEndpoints, asyncResp, swId)); 507 } 508 } 509 510 } // namespace sw_util 511 } // namespace redfish 512