1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 // SPDX-FileCopyrightText: Copyright 2019 Intel Corporation 4 #pragma once 5 6 #include "app.hpp" 7 #include "async_resp.hpp" 8 #include "error_messages.hpp" 9 #include "generated/enums/drive.hpp" 10 #include "generated/enums/protocol.hpp" 11 #include "generated/enums/resource.hpp" 12 #include "http_request.hpp" 13 #include "query.hpp" 14 #include "redfish_util.hpp" 15 #include "registries/privilege_registry.hpp" 16 17 namespace redfish 18 { 19 20 inline void getDrivePresent(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 21 const std::string& connectionName, 22 const std::string& path) 23 { 24 dbus::utility::getProperty<bool>( 25 connectionName, path, "xyz.openbmc_project.Inventory.Item", "Present", 26 [asyncResp, 27 path](const boost::system::error_code& ec, const bool isPresent) { 28 // this interface isn't necessary, only check it if 29 // we get a good return 30 if (ec) 31 { 32 return; 33 } 34 35 if (!isPresent) 36 { 37 asyncResp->res.jsonValue["Status"]["State"] = 38 resource::State::Absent; 39 } 40 }); 41 } 42 43 inline void getDriveState(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 44 const std::string& connectionName, 45 const std::string& path) 46 { 47 dbus::utility::getProperty<bool>( 48 connectionName, path, "xyz.openbmc_project.State.Drive", "Rebuilding", 49 [asyncResp](const boost::system::error_code& ec, const bool updating) { 50 // this interface isn't necessary, only check it 51 // if we get a good return 52 if (ec) 53 { 54 return; 55 } 56 57 // updating and disabled in the backend shouldn't be 58 // able to be set at the same time, so we don't need 59 // to check for the race condition of these two 60 // calls 61 if (updating) 62 { 63 asyncResp->res.jsonValue["Status"]["State"] = 64 resource::State::Updating; 65 } 66 }); 67 } 68 69 inline std::optional<drive::MediaType> convertDriveType(std::string_view type) 70 { 71 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.HDD") 72 { 73 return drive::MediaType::HDD; 74 } 75 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.SSD") 76 { 77 return drive::MediaType::SSD; 78 } 79 if (type == "xyz.openbmc_project.Inventory.Item.Drive.DriveType.Unknown") 80 { 81 return std::nullopt; 82 } 83 84 return drive::MediaType::Invalid; 85 } 86 87 inline std::optional<protocol::Protocol> convertDriveProtocol( 88 std::string_view proto) 89 { 90 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SAS") 91 { 92 return protocol::Protocol::SAS; 93 } 94 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.SATA") 95 { 96 return protocol::Protocol::SATA; 97 } 98 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe") 99 { 100 return protocol::Protocol::NVMe; 101 } 102 if (proto == "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.FC") 103 { 104 return protocol::Protocol::FC; 105 } 106 if (proto == 107 "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.Unknown") 108 { 109 return std::nullopt; 110 } 111 112 return protocol::Protocol::Invalid; 113 } 114 115 inline void getDriveItemProperties( 116 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 117 const std::string& connectionName, const std::string& path) 118 { 119 dbus::utility::getAllProperties( 120 connectionName, path, "xyz.openbmc_project.Inventory.Item.Drive", 121 [asyncResp](const boost::system::error_code& ec, 122 const std::vector< 123 std::pair<std::string, dbus::utility::DbusVariantType>>& 124 propertiesList) { 125 if (ec) 126 { 127 // this interface isn't required 128 return; 129 } 130 const std::string* encryptionStatus = nullptr; 131 const bool* isLocked = nullptr; 132 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 133 property : propertiesList) 134 { 135 const std::string& propertyName = property.first; 136 if (propertyName == "Type") 137 { 138 const std::string* value = 139 std::get_if<std::string>(&property.second); 140 if (value == nullptr) 141 { 142 // illegal property 143 BMCWEB_LOG_ERROR("Illegal property: Type"); 144 messages::internalError(asyncResp->res); 145 return; 146 } 147 148 std::optional<drive::MediaType> mediaType = 149 convertDriveType(*value); 150 if (!mediaType) 151 { 152 BMCWEB_LOG_WARNING("UnknownDriveType Interface: {}", 153 *value); 154 continue; 155 } 156 if (*mediaType == drive::MediaType::Invalid) 157 { 158 messages::internalError(asyncResp->res); 159 return; 160 } 161 162 asyncResp->res.jsonValue["MediaType"] = *mediaType; 163 } 164 else if (propertyName == "Capacity") 165 { 166 const uint64_t* capacity = 167 std::get_if<uint64_t>(&property.second); 168 if (capacity == nullptr) 169 { 170 BMCWEB_LOG_ERROR("Illegal property: Capacity"); 171 messages::internalError(asyncResp->res); 172 return; 173 } 174 if (*capacity == 0) 175 { 176 // drive capacity not known 177 continue; 178 } 179 180 asyncResp->res.jsonValue["CapacityBytes"] = *capacity; 181 } 182 else if (propertyName == "Protocol") 183 { 184 const std::string* value = 185 std::get_if<std::string>(&property.second); 186 if (value == nullptr) 187 { 188 BMCWEB_LOG_ERROR("Illegal property: Protocol"); 189 messages::internalError(asyncResp->res); 190 return; 191 } 192 193 std::optional<protocol::Protocol> proto = 194 convertDriveProtocol(*value); 195 if (!proto) 196 { 197 BMCWEB_LOG_WARNING( 198 "Unknown DrivePrototype Interface: {}", *value); 199 continue; 200 } 201 if (*proto == protocol::Protocol::Invalid) 202 { 203 messages::internalError(asyncResp->res); 204 return; 205 } 206 asyncResp->res.jsonValue["Protocol"] = *proto; 207 } 208 else if (propertyName == "PredictedMediaLifeLeftPercent") 209 { 210 const uint8_t* lifeLeft = 211 std::get_if<uint8_t>(&property.second); 212 if (lifeLeft == nullptr) 213 { 214 BMCWEB_LOG_ERROR( 215 "Illegal property: PredictedMediaLifeLeftPercent"); 216 messages::internalError(asyncResp->res); 217 return; 218 } 219 // 255 means reading the value is not supported 220 if (*lifeLeft != 255) 221 { 222 asyncResp->res 223 .jsonValue["PredictedMediaLifeLeftPercent"] = 224 *lifeLeft; 225 } 226 } 227 else if (propertyName == "EncryptionStatus") 228 { 229 encryptionStatus = 230 std::get_if<std::string>(&property.second); 231 if (encryptionStatus == nullptr) 232 { 233 BMCWEB_LOG_ERROR("Illegal property: EncryptionStatus"); 234 messages::internalError(asyncResp->res); 235 return; 236 } 237 } 238 else if (propertyName == "Locked") 239 { 240 isLocked = std::get_if<bool>(&property.second); 241 if (isLocked == nullptr) 242 { 243 BMCWEB_LOG_ERROR("Illegal property: Locked"); 244 messages::internalError(asyncResp->res); 245 return; 246 } 247 } 248 } 249 250 if (encryptionStatus == nullptr || isLocked == nullptr || 251 *encryptionStatus == 252 "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Unknown") 253 { 254 return; 255 } 256 if (*encryptionStatus != 257 "xyz.openbmc_project.Inventory.Item.Drive.DriveEncryptionState.Encrypted") 258 { 259 //"The drive is not currently encrypted." 260 asyncResp->res.jsonValue["EncryptionStatus"] = 261 drive::EncryptionStatus::Unencrypted; 262 return; 263 } 264 if (*isLocked) 265 { 266 //"The drive is currently encrypted and the data is not 267 // accessible to the user." 268 asyncResp->res.jsonValue["EncryptionStatus"] = 269 drive::EncryptionStatus::Locked; 270 return; 271 } 272 // if not locked 273 // "The drive is currently encrypted but the data is accessible 274 // to the user in unencrypted form." 275 asyncResp->res.jsonValue["EncryptionStatus"] = 276 drive::EncryptionStatus::Unlocked; 277 }); 278 } 279 280 inline void addAllDriveInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 281 const std::string& connectionName, 282 const std::string& path, 283 const std::vector<std::string>& interfaces) 284 { 285 for (const std::string& interface : interfaces) 286 { 287 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset") 288 { 289 asset_utils::getAssetInfo(asyncResp, connectionName, path, 290 ""_json_pointer, false); 291 } 292 else if (interface == "xyz.openbmc_project.Inventory.Item") 293 { 294 getDrivePresent(asyncResp, connectionName, path); 295 } 296 else if (interface == "xyz.openbmc_project.State.Drive") 297 { 298 getDriveState(asyncResp, connectionName, path); 299 } 300 else if (interface == "xyz.openbmc_project.Inventory.Item.Drive") 301 { 302 getDriveItemProperties(asyncResp, connectionName, path); 303 } 304 } 305 } 306 307 inline void afterGetSubtreeSystemsStorageDrive( 308 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 309 const std::string& driveId, const boost::system::error_code& ec, 310 const dbus::utility::MapperGetSubTreeResponse& subtree) 311 { 312 if (ec) 313 { 314 BMCWEB_LOG_ERROR("Drive mapper call error"); 315 messages::internalError(asyncResp->res); 316 return; 317 } 318 319 auto drive = std::ranges::find_if( 320 subtree, 321 [&driveId](const std::pair<std::string, 322 dbus::utility::MapperServiceMap>& object) { 323 return sdbusplus::message::object_path(object.first).filename() == 324 driveId; 325 }); 326 327 if (drive == subtree.end()) 328 { 329 messages::resourceNotFound(asyncResp->res, "Drive", driveId); 330 return; 331 } 332 333 const std::string& path = drive->first; 334 const dbus::utility::MapperServiceMap& connectionNames = drive->second; 335 336 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; 337 asyncResp->res.jsonValue["@odata.id"] = 338 boost::urls::format("/redfish/v1/Systems/{}/Storage/1/Drives/{}", 339 BMCWEB_REDFISH_SYSTEM_URI_NAME, driveId); 340 asyncResp->res.jsonValue["Name"] = driveId; 341 asyncResp->res.jsonValue["Id"] = driveId; 342 343 if (connectionNames.size() != 1) 344 { 345 BMCWEB_LOG_ERROR("Connection size {}, not equal to 1", 346 connectionNames.size()); 347 messages::internalError(asyncResp->res); 348 return; 349 } 350 351 getMainChassisId( 352 asyncResp, [](const std::string& chassisId, 353 const std::shared_ptr<bmcweb::AsyncResp>& aRsp) { 354 aRsp->res.jsonValue["Links"]["Chassis"]["@odata.id"] = 355 boost::urls::format("/redfish/v1/Chassis/{}", chassisId); 356 }); 357 358 // default it to Enabled 359 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 360 361 addAllDriveInfo(asyncResp, connectionNames[0].first, path, 362 connectionNames[0].second); 363 } 364 365 inline void afterChassisDriveCollectionSubtreeGet( 366 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 367 const std::string& chassisId, const boost::system::error_code& ec, 368 const dbus::utility::MapperGetSubTreeResponse& subtree) 369 { 370 if (ec) 371 { 372 if (ec == boost::system::errc::host_unreachable) 373 { 374 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 375 return; 376 } 377 messages::internalError(asyncResp->res); 378 return; 379 } 380 381 // Iterate over all retrieved ObjectPaths. 382 for (const auto& [path, connectionNames] : subtree) 383 { 384 sdbusplus::message::object_path objPath(path); 385 if (objPath.filename() != chassisId) 386 { 387 continue; 388 } 389 390 if (connectionNames.empty()) 391 { 392 BMCWEB_LOG_ERROR("Got 0 Connection names"); 393 continue; 394 } 395 396 asyncResp->res.jsonValue["@odata.type"] = 397 "#DriveCollection.DriveCollection"; 398 asyncResp->res.jsonValue["@odata.id"] = 399 boost::urls::format("/redfish/v1/Chassis/{}/Drives", chassisId); 400 asyncResp->res.jsonValue["Name"] = "Drive Collection"; 401 402 // Association lambda 403 dbus::utility::getAssociationEndPoints( 404 path + "/drive", 405 [asyncResp, chassisId](const boost::system::error_code& ec3, 406 const dbus::utility::MapperEndPoints& resp) { 407 if (ec3) 408 { 409 BMCWEB_LOG_ERROR("Error in chassis Drive association "); 410 } 411 nlohmann::json& members = asyncResp->res.jsonValue["Members"]; 412 // important if array is empty 413 members = nlohmann::json::array(); 414 415 std::vector<std::string> leafNames; 416 for (const auto& drive : resp) 417 { 418 sdbusplus::message::object_path drivePath(drive); 419 leafNames.push_back(drivePath.filename()); 420 } 421 422 std::ranges::sort(leafNames, AlphanumLess<std::string>()); 423 424 for (const auto& leafName : leafNames) 425 { 426 nlohmann::json::object_t member; 427 member["@odata.id"] = 428 boost::urls::format("/redfish/v1/Chassis/{}/Drives/{}", 429 chassisId, leafName); 430 members.emplace_back(std::move(member)); 431 // navigation links will be registered in next patch set 432 } 433 asyncResp->res.jsonValue["Members@odata.count"] = resp.size(); 434 }); // end association lambda 435 436 } // end Iterate over all retrieved ObjectPaths 437 } 438 439 /** 440 * Chassis drives, this URL will show all the DriveCollection 441 * information 442 */ 443 inline void chassisDriveCollectionGet( 444 crow::App& app, const crow::Request& req, 445 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 446 const std::string& chassisId) 447 { 448 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 449 { 450 return; 451 } 452 453 // mapper call lambda 454 dbus::utility::getSubTree( 455 "/xyz/openbmc_project/inventory", 0, chassisInterfaces, 456 std::bind_front(afterChassisDriveCollectionSubtreeGet, asyncResp, 457 chassisId)); 458 } 459 460 inline void buildDrive(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 461 const std::string& chassisId, 462 const std::string& driveName, 463 const boost::system::error_code& ec, 464 const dbus::utility::MapperGetSubTreeResponse& subtree) 465 { 466 if (ec) 467 { 468 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 469 messages::internalError(asyncResp->res); 470 return; 471 } 472 473 // Iterate over all retrieved ObjectPaths. 474 for (const auto& [path, connectionNames] : subtree) 475 { 476 sdbusplus::message::object_path objPath(path); 477 if (objPath.filename() != driveName) 478 { 479 continue; 480 } 481 482 if (connectionNames.empty()) 483 { 484 BMCWEB_LOG_ERROR("Got 0 Connection names"); 485 continue; 486 } 487 488 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 489 "/redfish/v1/Chassis/{}/Drives/{}", chassisId, driveName); 490 491 asyncResp->res.jsonValue["@odata.type"] = "#Drive.v1_7_0.Drive"; 492 asyncResp->res.jsonValue["Name"] = driveName; 493 asyncResp->res.jsonValue["Id"] = driveName; 494 // default it to Enabled 495 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled; 496 497 nlohmann::json::object_t linkChassisNav; 498 linkChassisNav["@odata.id"] = 499 boost::urls::format("/redfish/v1/Chassis/{}", chassisId); 500 asyncResp->res.jsonValue["Links"]["Chassis"] = linkChassisNav; 501 502 addAllDriveInfo(asyncResp, connectionNames[0].first, path, 503 connectionNames[0].second); 504 } 505 } 506 507 inline void matchAndFillDrive( 508 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 509 const std::string& chassisId, const std::string& driveName, 510 const std::vector<std::string>& resp) 511 { 512 for (const std::string& drivePath : resp) 513 { 514 sdbusplus::message::object_path path(drivePath); 515 std::string leaf = path.filename(); 516 if (leaf != driveName) 517 { 518 continue; 519 } 520 // mapper call drive 521 constexpr std::array<std::string_view, 1> driveInterface = { 522 "xyz.openbmc_project.Inventory.Item.Drive"}; 523 dbus::utility::getSubTree( 524 "/xyz/openbmc_project/inventory", 0, driveInterface, 525 [asyncResp, chassisId, driveName]( 526 const boost::system::error_code& ec, 527 const dbus::utility::MapperGetSubTreeResponse& subtree) { 528 buildDrive(asyncResp, chassisId, driveName, ec, subtree); 529 }); 530 } 531 } 532 533 inline void handleChassisDriveGet( 534 crow::App& app, const crow::Request& req, 535 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 536 const std::string& chassisId, const std::string& driveName) 537 { 538 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 539 { 540 return; 541 } 542 543 // mapper call chassis 544 dbus::utility::getSubTree( 545 "/xyz/openbmc_project/inventory", 0, chassisInterfaces, 546 [asyncResp, chassisId, 547 driveName](const boost::system::error_code& ec, 548 const dbus::utility::MapperGetSubTreeResponse& subtree) { 549 if (ec) 550 { 551 messages::internalError(asyncResp->res); 552 return; 553 } 554 555 // Iterate over all retrieved ObjectPaths. 556 for (const auto& [path, connectionNames] : subtree) 557 { 558 sdbusplus::message::object_path objPath(path); 559 if (objPath.filename() != chassisId) 560 { 561 continue; 562 } 563 564 if (connectionNames.empty()) 565 { 566 BMCWEB_LOG_ERROR("Got 0 Connection names"); 567 continue; 568 } 569 570 dbus::utility::getAssociationEndPoints( 571 path + "/drive", 572 [asyncResp, chassisId, 573 driveName](const boost::system::error_code& ec3, 574 const dbus::utility::MapperEndPoints& resp) { 575 if (ec3) 576 { 577 return; // no drives = no failures 578 } 579 matchAndFillDrive(asyncResp, chassisId, driveName, 580 resp); 581 }); 582 return; 583 } 584 // Couldn't find an object with that name. return an error 585 messages::resourceNotFound(asyncResp->res, "Chassis", chassisId); 586 }); 587 } 588 589 /** 590 * This URL will show the drive interface for the specific drive in the chassis 591 */ 592 inline void requestRoutesChassisDrive(App& app) 593 { 594 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/") 595 .privileges(redfish::privileges::getDriveCollection) 596 .methods(boost::beast::http::verb::get)( 597 std::bind_front(chassisDriveCollectionGet, std::ref(app))); 598 599 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Drives/<str>/") 600 .privileges(redfish::privileges::getChassis) 601 .methods(boost::beast::http::verb::get)( 602 std::bind_front(handleChassisDriveGet, std::ref(app))); 603 } 604 605 } // namespace redfish 606