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 sdbusplus::asio::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 sdbusplus::asio::getAllProperties( 381 *crow::connections::systemBus, dbusSvc, 382 "/xyz/openbmc_project/software/" + *swId, 383 "xyz.openbmc_project.Software.Activation", 384 [asyncResp, 385 swId](const boost::system::error_code& ec, 386 const dbus::utility::DBusPropertiesMap& propertiesList) { 387 if (ec) 388 { 389 // not all swtypes are updateable, this is ok 390 asyncResp->res.jsonValue["Status"]["State"] = 391 resource::State::Enabled; 392 return; 393 } 394 395 const std::string* swInvActivation = nullptr; 396 397 const bool success = sdbusplus::unpackPropertiesNoThrow( 398 dbus_utils::UnpackErrorPrinter(), propertiesList, "Activation", 399 swInvActivation); 400 401 if (!success) 402 { 403 messages::internalError(asyncResp->res); 404 return; 405 } 406 407 if (swInvActivation == nullptr) 408 { 409 messages::internalError(asyncResp->res); 410 return; 411 } 412 413 BMCWEB_LOG_DEBUG("getSwStatus: Activation {}", *swInvActivation); 414 asyncResp->res.jsonValue["Status"]["State"] = 415 getRedfishSwState(*swInvActivation); 416 asyncResp->res.jsonValue["Status"]["Health"] = 417 getRedfishSwHealth(*swInvActivation); 418 }); 419 } 420 421 inline void handleUpdateableEndpoints( 422 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 423 const std::shared_ptr<std::string>& swId, 424 const boost::system::error_code& ec, 425 const dbus::utility::MapperEndPoints& objPaths) 426 { 427 if (ec) 428 { 429 BMCWEB_LOG_DEBUG(" error_code = {} error msg = {}", ec, ec.message()); 430 // System can exist with no updateable software, 431 // so don't throw error here. 432 return; 433 } 434 sdbusplus::message::object_path reqSwObjPath( 435 "/xyz/openbmc_project/software"); 436 reqSwObjPath = reqSwObjPath / *swId; 437 438 if (std::ranges::find(objPaths, reqSwObjPath.str) != objPaths.end()) 439 { 440 asyncResp->res.jsonValue["Updateable"] = true; 441 return; 442 } 443 } 444 445 inline void 446 handleUpdateableObject(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 447 const boost::system::error_code& ec, 448 const dbus::utility::MapperGetObject& objectInfo) 449 { 450 if (ec) 451 { 452 BMCWEB_LOG_DEBUG(" error_code = {} error msg = {}", ec, ec.message()); 453 // System can exist with no updateable software, 454 // so don't throw error here. 455 return; 456 } 457 if (objectInfo.empty()) 458 { 459 BMCWEB_LOG_DEBUG("No updateable software found"); 460 // System can exist with no updateable software, 461 // so don't throw error here. 462 return; 463 } 464 asyncResp->res.jsonValue["Updateable"] = true; 465 } 466 467 /** 468 * @brief Updates programmable status of input swId into json response 469 * 470 * This function checks whether software inventory component 471 * can be programmable or not and fill's the "Updatable" 472 * Property. 473 * 474 * @param[i,o] asyncResp Async response object 475 * @param[i] swId The software ID 476 */ 477 inline void 478 getSwUpdatableStatus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 479 const std::shared_ptr<std::string>& swId) 480 { 481 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 482 { 483 sdbusplus::message::object_path swObjectPath( 484 "/xyz/openbmc_project/software"); 485 swObjectPath = swObjectPath / *swId; 486 constexpr std::array<std::string_view, 1> interfaces = { 487 "xyz.openbmc_project.Software.Update"}; 488 dbus::utility::getDbusObject( 489 swObjectPath.str, interfaces, 490 std::bind_front(handleUpdateableObject, asyncResp)); 491 } 492 else 493 { 494 dbus::utility::getAssociationEndPoints( 495 "/xyz/openbmc_project/software/updateable", 496 std::bind_front(handleUpdateableEndpoints, asyncResp, swId)); 497 } 498 } 499 500 } // namespace sw_util 501 } // namespace redfish 502