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 BMCWEB_LOG_ERROR("error_code = {}", ec); 93 BMCWEB_LOG_ERROR("error msg = {}", ec.message()); 94 // Have seen the code update app delete the 95 // D-Bus object, during code update, between 96 // the call to mapper and here. Just leave 97 // these properties off if resource not 98 // found. 99 if (ec.value() == EBADR) 100 { 101 return; 102 } 103 messages::internalError(asyncResp->res); 104 return; 105 } 106 // example propertiesList 107 // a{sv} 2 "Version" s 108 // "IBM-witherspoon-OP9-v2.0.10-2.22" "Purpose" 109 // s 110 // "xyz.openbmc_project.Software.Version.VersionPurpose.Host" 111 const std::string* version = nullptr; 112 const std::string* swInvPurpose = nullptr; 113 114 const bool success = sdbusplus::unpackPropertiesNoThrow( 115 dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose", 116 swInvPurpose, "Version", version); 117 118 if (!success) 119 { 120 messages::internalError(asyncResp->res); 121 return; 122 } 123 124 if (version == nullptr || version->empty()) 125 { 126 messages::internalError(asyncResp->res); 127 return; 128 } 129 if (swInvPurpose == nullptr || *swInvPurpose != swVersionPurpose) 130 { 131 // Not purpose we're looking for 132 return; 133 } 134 135 BMCWEB_LOG_DEBUG("Image ID: {}", swId); 136 BMCWEB_LOG_DEBUG("Running image: {}", runningImage); 137 BMCWEB_LOG_DEBUG("Image purpose: {}", *swInvPurpose); 138 139 if (populateLinkToImages) 140 { 141 nlohmann::json& softwareImageMembers = 142 asyncResp->res.jsonValue["Links"]["SoftwareImages"]; 143 // Firmware images are at 144 // /redfish/v1/UpdateService/FirmwareInventory/<Id> 145 // e.g. .../FirmwareInventory/82d3ec86 146 nlohmann::json::object_t member; 147 member["@odata.id"] = boost::urls::format( 148 "/redfish/v1/UpdateService/FirmwareInventory/{}", swId); 149 softwareImageMembers.emplace_back(std::move(member)); 150 asyncResp->res.jsonValue["Links"]["SoftwareImages@odata.count"] = 151 softwareImageMembers.size(); 152 153 if (runningImage) 154 { 155 nlohmann::json::object_t runningMember; 156 runningMember["@odata.id"] = boost::urls::format( 157 "/redfish/v1/UpdateService/FirmwareInventory/{}", swId); 158 // Create the link to the running image 159 asyncResp->res.jsonValue["Links"]["ActiveSoftwareImage"] = 160 std::move(runningMember); 161 } 162 } 163 if (!activeVersionPropName.empty() && runningImage) 164 { 165 asyncResp->res.jsonValue[activeVersionPropName] = *version; 166 } 167 } 168 169 inline void afterGetSubtree( 170 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 171 const std::string& swVersionPurpose, 172 const std::string& activeVersionPropName, bool populateLinkToImages, 173 const std::vector<std::string>& functionalSwIds, 174 const boost::system::error_code& ec, 175 const dbus::utility::MapperGetSubTreeResponse& subtree) 176 { 177 if (ec) 178 { 179 BMCWEB_LOG_ERROR("error_code = {}", ec); 180 BMCWEB_LOG_ERROR("error msg = {}", ec.message()); 181 messages::internalError(asyncResp->res); 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 BMCWEB_LOG_DEBUG("error msg = {}", ec.message()); 240 // No functional software for this swVersionPurpose, so just 241 return; 242 } 243 244 if (functionalSw.empty()) 245 { 246 // Could keep going and try to populate SoftwareImages 247 // but something is seriously wrong, so just fail 248 BMCWEB_LOG_ERROR("Zero functional software in system"); 249 messages::internalError(asyncResp->res); 250 return; 251 } 252 253 std::vector<std::string> functionalSwIds; 254 // example functionalSw: 255 // v as 2 "/xyz/openbmc_project/software/ace821ef" 256 // "/xyz/openbmc_project/software/230fb078" 257 for (const auto& sw : functionalSw) 258 { 259 sdbusplus::message::object_path path(sw); 260 std::string leaf = path.filename(); 261 if (leaf.empty()) 262 { 263 continue; 264 } 265 266 functionalSwIds.push_back(leaf); 267 } 268 269 constexpr std::array<std::string_view, 1> interfaces = { 270 "xyz.openbmc_project.Software.Version"}; 271 dbus::utility::getSubTree( 272 "/xyz/openbmc_project/software", 0, interfaces, 273 [asyncResp, swVersionPurpose, activeVersionPropName, 274 populateLinkToImages, functionalSwIds]( 275 const boost::system::error_code& ec2, 276 const dbus::utility::MapperGetSubTreeResponse& subtree) { 277 afterGetSubtree(asyncResp, swVersionPurpose, activeVersionPropName, 278 populateLinkToImages, functionalSwIds, ec2, 279 subtree); 280 }); 281 } 282 283 /** 284 * @brief Populate the running software version and image links 285 * 286 * @param[i,o] asyncResp Async response object 287 * @param[i] swVersionPurpose Indicates what target to look for 288 * @param[i] activeVersionPropName Index in asyncResp->res.jsonValue to write 289 * the running software version to 290 * @param[i] populateLinkToImages Populate asyncResp->res "Links" 291 * "ActiveSoftwareImage" with a link to the running software image and 292 * "SoftwareImages" with a link to the all its software images 293 * 294 * @return void 295 */ 296 inline void populateSoftwareInformation( 297 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 298 const std::string& swVersionPurpose, 299 const std::string& activeVersionPropName, const bool populateLinkToImages) 300 { 301 auto swPath = getFunctionalSoftwarePath(swVersionPurpose); 302 if (!swPath) 303 { 304 BMCWEB_LOG_ERROR("Invalid software type"); 305 messages::internalError(asyncResp->res); 306 return; 307 } 308 // Used later to determine running (known on Redfish as active) Sw images 309 dbus::utility::getAssociationEndPoints( 310 swPath.value().str, 311 [asyncResp, swVersionPurpose, activeVersionPropName, 312 populateLinkToImages]( 313 const boost::system::error_code& ec, 314 const dbus::utility::MapperEndPoints& functionalSw) { 315 afterAssociatedEndpoints(asyncResp, swVersionPurpose, 316 activeVersionPropName, 317 populateLinkToImages, ec, functionalSw); 318 }); 319 } 320 321 /** 322 * @brief Translate input swState to Redfish state 323 * 324 * This function will return the corresponding Redfish state 325 * 326 * @param[i] swState The OpenBMC software state 327 * 328 * @return The corresponding Redfish state 329 */ 330 inline resource::State getRedfishSwState(const std::string& swState) 331 { 332 if (swState == "xyz.openbmc_project.Software.Activation.Activations.Active") 333 { 334 return resource::State::Enabled; 335 } 336 if (swState == "xyz.openbmc_project.Software.Activation." 337 "Activations.Activating") 338 { 339 return resource::State::Updating; 340 } 341 if (swState == "xyz.openbmc_project.Software.Activation." 342 "Activations.StandbySpare") 343 { 344 return resource::State::StandbySpare; 345 } 346 BMCWEB_LOG_DEBUG("Default sw state {} to Disabled", swState); 347 return resource::State::Disabled; 348 } 349 350 /** 351 * @brief Translate input swState to Redfish health state 352 * 353 * This function will return the corresponding Redfish health state 354 * 355 * @param[i] swState The OpenBMC software state 356 * 357 * @return The corresponding Redfish health state 358 */ 359 inline std::string getRedfishSwHealth(const std::string& swState) 360 { 361 if ((swState == 362 "xyz.openbmc_project.Software.Activation.Activations.Active") || 363 (swState == "xyz.openbmc_project.Software.Activation.Activations." 364 "Activating") || 365 (swState == 366 "xyz.openbmc_project.Software.Activation.Activations.Ready")) 367 { 368 return "OK"; 369 } 370 BMCWEB_LOG_DEBUG("Sw state {} to Warning", swState); 371 return "Warning"; 372 } 373 374 /** 375 * @brief Put status of input swId into json response 376 * 377 * This function will put the appropriate Redfish state of the input 378 * software id to ["Status"]["State"] within the json response 379 * 380 * @param[i,o] asyncResp Async response object 381 * @param[i] swId The software ID to get status for 382 * @param[i] dbusSvc The dbus service implementing the software object 383 * 384 * @return void 385 */ 386 inline void getSwStatus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 387 const std::shared_ptr<std::string>& swId, 388 const std::string& dbusSvc) 389 { 390 BMCWEB_LOG_DEBUG("getSwStatus: swId {} svc {}", *swId, dbusSvc); 391 392 dbus::utility::getAllProperties( 393 dbusSvc, "/xyz/openbmc_project/software/" + *swId, 394 "xyz.openbmc_project.Software.Activation", 395 [asyncResp, 396 swId](const boost::system::error_code& ec, 397 const dbus::utility::DBusPropertiesMap& propertiesList) { 398 if (ec) 399 { 400 // not all swtypes are updateable, this is ok 401 asyncResp->res.jsonValue["Status"]["State"] = 402 resource::State::Enabled; 403 return; 404 } 405 406 const std::string* swInvActivation = nullptr; 407 408 const bool success = sdbusplus::unpackPropertiesNoThrow( 409 dbus_utils::UnpackErrorPrinter(), propertiesList, "Activation", 410 swInvActivation); 411 412 if (!success) 413 { 414 messages::internalError(asyncResp->res); 415 return; 416 } 417 418 if (swInvActivation == nullptr) 419 { 420 messages::internalError(asyncResp->res); 421 return; 422 } 423 424 BMCWEB_LOG_DEBUG("getSwStatus: Activation {}", *swInvActivation); 425 asyncResp->res.jsonValue["Status"]["State"] = 426 getRedfishSwState(*swInvActivation); 427 asyncResp->res.jsonValue["Status"]["Health"] = 428 getRedfishSwHealth(*swInvActivation); 429 }); 430 } 431 432 inline void handleUpdateableEndpoints( 433 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 434 const std::shared_ptr<std::string>& swId, 435 const boost::system::error_code& ec, 436 const dbus::utility::MapperEndPoints& objPaths) 437 { 438 if (ec) 439 { 440 BMCWEB_LOG_DEBUG(" error_code = {} error msg = {}", ec, ec.message()); 441 // System can exist with no updateable software, 442 // so don't throw error here. 443 return; 444 } 445 sdbusplus::message::object_path reqSwObjPath( 446 "/xyz/openbmc_project/software"); 447 reqSwObjPath = reqSwObjPath / *swId; 448 449 if (std::ranges::find(objPaths, reqSwObjPath.str) != objPaths.end()) 450 { 451 asyncResp->res.jsonValue["Updateable"] = true; 452 return; 453 } 454 } 455 456 inline void handleUpdateableObject( 457 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 458 const boost::system::error_code& ec, 459 const dbus::utility::MapperGetObject& objectInfo) 460 { 461 if (ec) 462 { 463 BMCWEB_LOG_DEBUG(" error_code = {} error msg = {}", ec, ec.message()); 464 // System can exist with no updateable software, 465 // so don't throw error here. 466 return; 467 } 468 if (objectInfo.empty()) 469 { 470 BMCWEB_LOG_DEBUG("No updateable software found"); 471 // System can exist with no updateable software, 472 // so don't throw error here. 473 return; 474 } 475 asyncResp->res.jsonValue["Updateable"] = true; 476 } 477 478 /** 479 * @brief Updates programmable status of input swId into json response 480 * 481 * This function checks whether software inventory component 482 * can be programmable or not and fill's the "Updatable" 483 * Property. 484 * 485 * @param[i,o] asyncResp Async response object 486 * @param[i] swId The software ID 487 */ 488 inline void getSwUpdatableStatus( 489 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 490 const std::shared_ptr<std::string>& swId) 491 { 492 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 493 { 494 sdbusplus::message::object_path swObjectPath( 495 "/xyz/openbmc_project/software"); 496 swObjectPath = swObjectPath / *swId; 497 constexpr std::array<std::string_view, 1> interfaces = { 498 "xyz.openbmc_project.Software.Update"}; 499 dbus::utility::getDbusObject( 500 swObjectPath.str, interfaces, 501 std::bind_front(handleUpdateableObject, asyncResp)); 502 } 503 else 504 { 505 dbus::utility::getAssociationEndPoints( 506 "/xyz/openbmc_project/software/updateable", 507 std::bind_front(handleUpdateableEndpoints, asyncResp, swId)); 508 } 509 } 510 511 } // namespace sw_util 512 } // namespace redfish 513