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