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