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