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