1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 #pragma once 4 5 #include "bmcweb_config.h" 6 7 #include "app.hpp" 8 #include "async_resp.hpp" 9 #include "dbus_singleton.hpp" 10 #include "dbus_utility.hpp" 11 #include "error_messages.hpp" 12 #include "generated/enums/log_service.hpp" 13 #include "http_request.hpp" 14 #include "http_utility.hpp" 15 #include "logging.hpp" 16 #include "query.hpp" 17 #include "registries.hpp" 18 #include "registries/privilege_registry.hpp" 19 #include "str_utility.hpp" 20 #include "utility.hpp" 21 #include "utils/hex_utils.hpp" 22 #include "utils/query_param.hpp" 23 #include "utils/time_utils.hpp" 24 25 #include <asm-generic/errno.h> 26 27 #include <boost/beast/http/field.hpp> 28 #include <boost/beast/http/status.hpp> 29 #include <boost/beast/http/verb.hpp> 30 #include <boost/container/flat_map.hpp> 31 #include <boost/url/format.hpp> 32 33 #include <algorithm> 34 #include <array> 35 #include <charconv> 36 #include <cstddef> 37 #include <cstdint> 38 #include <format> 39 #include <functional> 40 #include <iomanip> 41 #include <ios> 42 #include <memory> 43 #include <sstream> 44 #include <string> 45 #include <string_view> 46 #include <system_error> 47 #include <tuple> 48 #include <utility> 49 #include <vector> 50 51 namespace redfish 52 { 53 54 inline void handleSystemsLogServicesPostCodesGet( 55 App& app, const crow::Request& req, 56 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 57 const std::string& systemName) 58 { 59 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 60 { 61 return; 62 } 63 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 64 { 65 // Option currently returns no systems. TBD 66 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 67 systemName); 68 return; 69 } 70 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 71 { 72 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 73 systemName); 74 return; 75 } 76 asyncResp->res.jsonValue["@odata.id"] = 77 std::format("/redfish/v1/Systems/{}/LogServices/PostCodes", 78 BMCWEB_REDFISH_SYSTEM_URI_NAME); 79 asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService"; 80 asyncResp->res.jsonValue["Name"] = "POST Code Log Service"; 81 asyncResp->res.jsonValue["Description"] = "POST Code Log Service"; 82 asyncResp->res.jsonValue["Id"] = "PostCodes"; 83 asyncResp->res.jsonValue["OverWritePolicy"] = 84 log_service::OverWritePolicy::WrapsWhenFull; 85 asyncResp->res.jsonValue["Entries"]["@odata.id"] = 86 std::format("/redfish/v1/Systems/{}/LogServices/PostCodes/Entries", 87 BMCWEB_REDFISH_SYSTEM_URI_NAME); 88 89 std::pair<std::string, std::string> redfishDateTimeOffset = 90 redfish::time_utils::getDateTimeOffsetNow(); 91 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 92 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 93 redfishDateTimeOffset.second; 94 95 asyncResp->res 96 .jsonValue["Actions"]["#LogService.ClearLog"]["target"] = std::format( 97 "/redfish/v1/Systems/{}/LogServices/PostCodes/Actions/LogService.ClearLog", 98 BMCWEB_REDFISH_SYSTEM_URI_NAME); 99 } 100 101 inline void handleSystemsLogServicesPostCodesPost( 102 App& app, const crow::Request& req, 103 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 104 const std::string& systemName) 105 { 106 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 107 { 108 return; 109 } 110 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 111 { 112 // Option currently returns no systems. TBD 113 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 114 systemName); 115 return; 116 } 117 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 118 { 119 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 120 systemName); 121 return; 122 } 123 BMCWEB_LOG_DEBUG("Do delete all postcodes entries."); 124 125 // Make call to post-code service to request clear all 126 crow::connections::systemBus->async_method_call( 127 [asyncResp](const boost::system::error_code& ec) { 128 if (ec) 129 { 130 // TODO Handle for specific error code 131 BMCWEB_LOG_ERROR("doClearPostCodes resp_handler got error {}", 132 ec); 133 asyncResp->res.result( 134 boost::beast::http::status::internal_server_error); 135 messages::internalError(asyncResp->res); 136 return; 137 } 138 messages::success(asyncResp->res); 139 }, 140 "xyz.openbmc_project.State.Boot.PostCode0", 141 "/xyz/openbmc_project/State/Boot/PostCode0", 142 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 143 } 144 145 /** 146 * @brief Parse post code ID and get the current value and index value 147 * eg: postCodeID=B1-2, currentValue=1, index=2 148 * 149 * @param[in] postCodeID Post Code ID 150 * @param[out] currentValue Current value 151 * @param[out] index Index value 152 * 153 * @return bool true if the parsing is successful, false the parsing fails 154 */ 155 inline bool parsePostCode(std::string_view postCodeID, uint64_t& currentValue, 156 uint16_t& index) 157 { 158 std::vector<std::string> split; 159 bmcweb::split(split, postCodeID, '-'); 160 if (split.size() != 2) 161 { 162 return false; 163 } 164 std::string_view postCodeNumber = split[0]; 165 if (postCodeNumber.size() < 2) 166 { 167 return false; 168 } 169 if (postCodeNumber[0] != 'B') 170 { 171 return false; 172 } 173 postCodeNumber.remove_prefix(1); 174 auto [ptrIndex, ecIndex] = 175 std::from_chars(postCodeNumber.begin(), postCodeNumber.end(), index); 176 if (ptrIndex != postCodeNumber.end() || ecIndex != std::errc()) 177 { 178 return false; 179 } 180 181 std::string_view postCodeIndex = split[1]; 182 183 auto [ptrValue, ecValue] = std::from_chars( 184 postCodeIndex.begin(), postCodeIndex.end(), currentValue); 185 186 return ptrValue == postCodeIndex.end() && ecValue == std::errc(); 187 } 188 189 static bool fillPostCodeEntry( 190 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 191 const boost::container::flat_map< 192 uint64_t, std::tuple<std::vector<uint8_t>, std::vector<uint8_t>>>& 193 postcode, 194 const uint16_t bootIndex, const uint64_t codeIndex = 0, 195 const uint64_t skip = 0, const uint64_t top = 0) 196 { 197 // Get the Message from the MessageRegistry 198 const registries::Message* message = 199 registries::getMessage("OpenBMC.0.2.BIOSPOSTCode"); 200 if (message == nullptr) 201 { 202 BMCWEB_LOG_ERROR("Couldn't find known message?"); 203 return false; 204 } 205 uint64_t currentCodeIndex = 0; 206 uint64_t firstCodeTimeUs = 0; 207 for (const std::pair<uint64_t, std::tuple<std::vector<uint8_t>, 208 std::vector<uint8_t>>>& code : 209 postcode) 210 { 211 currentCodeIndex++; 212 std::string postcodeEntryID = 213 "B" + std::to_string(bootIndex) + "-" + 214 std::to_string(currentCodeIndex); // 1 based index in EntryID string 215 216 uint64_t usecSinceEpoch = code.first; 217 uint64_t usTimeOffset = 0; 218 219 if (1 == currentCodeIndex) 220 { // already incremented 221 firstCodeTimeUs = code.first; 222 } 223 else 224 { 225 usTimeOffset = code.first - firstCodeTimeUs; 226 } 227 228 // skip if no specific codeIndex is specified and currentCodeIndex does 229 // not fall between top and skip 230 if ((codeIndex == 0) && 231 (currentCodeIndex <= skip || currentCodeIndex > top)) 232 { 233 continue; 234 } 235 236 // skip if a specific codeIndex is specified and does not match the 237 // currentIndex 238 if ((codeIndex > 0) && (currentCodeIndex != codeIndex)) 239 { 240 // This is done for simplicity. 1st entry is needed to calculate 241 // time offset. To improve efficiency, one can get to the entry 242 // directly (possibly with flatmap's nth method) 243 continue; 244 } 245 246 // currentCodeIndex is within top and skip or equal to specified code 247 // index 248 249 // Get the Created time from the timestamp 250 std::string entryTimeStr; 251 entryTimeStr = redfish::time_utils::getDateTimeUintUs(usecSinceEpoch); 252 253 // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex) 254 std::ostringstream timeOffsetStr; 255 // Set Fixed -Point Notation 256 timeOffsetStr << std::fixed; 257 // Set precision to 4 digits 258 timeOffsetStr << std::setprecision(4); 259 // Add double to stream 260 timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000; 261 262 std::string bootIndexStr = std::to_string(bootIndex); 263 std::string timeOffsetString = timeOffsetStr.str(); 264 std::string hexCodeStr = 265 "0x" + bytesToHexString(std::get<0>(code.second)); 266 267 std::array<std::string_view, 3> messageArgs = { 268 bootIndexStr, timeOffsetString, hexCodeStr}; 269 270 std::string msg = 271 redfish::registries::fillMessageArgs(messageArgs, message->message); 272 if (msg.empty()) 273 { 274 messages::internalError(asyncResp->res); 275 return false; 276 } 277 278 // Get Severity template from message registry 279 std::string severity; 280 if (message != nullptr) 281 { 282 severity = message->messageSeverity; 283 } 284 285 // Format entry 286 nlohmann::json::object_t bmcLogEntry; 287 bmcLogEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 288 bmcLogEntry["@odata.id"] = boost::urls::format( 289 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries/{}", 290 BMCWEB_REDFISH_SYSTEM_URI_NAME, postcodeEntryID); 291 bmcLogEntry["Name"] = "POST Code Log Entry"; 292 bmcLogEntry["Id"] = postcodeEntryID; 293 bmcLogEntry["Message"] = std::move(msg); 294 bmcLogEntry["MessageId"] = "OpenBMC.0.2.BIOSPOSTCode"; 295 bmcLogEntry["MessageArgs"] = messageArgs; 296 bmcLogEntry["EntryType"] = "Event"; 297 bmcLogEntry["Severity"] = std::move(severity); 298 bmcLogEntry["Created"] = entryTimeStr; 299 if (!std::get<1>(code.second).empty()) 300 { 301 bmcLogEntry["AdditionalDataURI"] = 302 std::format( 303 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries/", 304 BMCWEB_REDFISH_SYSTEM_URI_NAME) + 305 postcodeEntryID + "/attachment"; 306 } 307 308 // codeIndex is only specified when querying single entry, return only 309 // that entry in this case 310 if (codeIndex != 0) 311 { 312 asyncResp->res.jsonValue.update(bmcLogEntry); 313 return true; 314 } 315 316 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 317 logEntryArray.emplace_back(std::move(bmcLogEntry)); 318 } 319 320 // Return value is always false when querying multiple entries 321 return false; 322 } 323 324 inline void 325 getPostCodeForEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 326 const std::string& entryId) 327 { 328 uint16_t bootIndex = 0; 329 uint64_t codeIndex = 0; 330 if (!parsePostCode(entryId, codeIndex, bootIndex)) 331 { 332 // Requested ID was not found 333 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 334 return; 335 } 336 337 if (bootIndex == 0 || codeIndex == 0) 338 { 339 // 0 is an invalid index 340 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 341 return; 342 } 343 344 crow::connections::systemBus->async_method_call( 345 [asyncResp, entryId, bootIndex, 346 codeIndex](const boost::system::error_code& ec, 347 const boost::container::flat_map< 348 uint64_t, std::tuple<std::vector<uint8_t>, 349 std::vector<uint8_t>>>& postcode) { 350 if (ec) 351 { 352 BMCWEB_LOG_DEBUG("DBUS POST CODE PostCode response error"); 353 messages::internalError(asyncResp->res); 354 return; 355 } 356 357 if (postcode.empty()) 358 { 359 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 360 return; 361 } 362 363 if (!fillPostCodeEntry(asyncResp, postcode, bootIndex, codeIndex)) 364 { 365 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 366 return; 367 } 368 }, 369 "xyz.openbmc_project.State.Boot.PostCode0", 370 "/xyz/openbmc_project/State/Boot/PostCode0", 371 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 372 bootIndex); 373 } 374 375 inline void 376 getPostCodeForBoot(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 377 const uint16_t bootIndex, const uint16_t bootCount, 378 const uint64_t entryCount, size_t skip, size_t top) 379 { 380 crow::connections::systemBus->async_method_call( 381 [asyncResp, bootIndex, bootCount, entryCount, skip, 382 top](const boost::system::error_code& ec, 383 const boost::container::flat_map< 384 uint64_t, std::tuple<std::vector<uint8_t>, 385 std::vector<uint8_t>>>& postcode) { 386 if (ec) 387 { 388 BMCWEB_LOG_DEBUG("DBUS POST CODE PostCode response error"); 389 messages::internalError(asyncResp->res); 390 return; 391 } 392 393 uint64_t endCount = entryCount; 394 if (!postcode.empty()) 395 { 396 endCount = entryCount + postcode.size(); 397 if (skip < endCount && (top + skip) > entryCount) 398 { 399 uint64_t thisBootSkip = 400 std::max(static_cast<uint64_t>(skip), entryCount) - 401 entryCount; 402 uint64_t thisBootTop = 403 std::min(static_cast<uint64_t>(top + skip), endCount) - 404 entryCount; 405 406 fillPostCodeEntry(asyncResp, postcode, bootIndex, 0, 407 thisBootSkip, thisBootTop); 408 } 409 asyncResp->res.jsonValue["Members@odata.count"] = endCount; 410 } 411 412 // continue to previous bootIndex 413 if (bootIndex < bootCount) 414 { 415 getPostCodeForBoot(asyncResp, 416 static_cast<uint16_t>(bootIndex + 1), 417 bootCount, endCount, skip, top); 418 } 419 else if (skip + top < endCount) 420 { 421 asyncResp->res.jsonValue["Members@odata.nextLink"] = 422 std::format( 423 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries?$skip=", 424 BMCWEB_REDFISH_SYSTEM_URI_NAME) + 425 std::to_string(skip + top); 426 } 427 }, 428 "xyz.openbmc_project.State.Boot.PostCode0", 429 "/xyz/openbmc_project/State/Boot/PostCode0", 430 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 431 bootIndex); 432 } 433 434 inline void 435 getCurrentBootNumber(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 436 size_t skip, size_t top) 437 { 438 uint64_t entryCount = 0; 439 dbus::utility::getProperty<uint16_t>( 440 "xyz.openbmc_project.State.Boot.PostCode0", 441 "/xyz/openbmc_project/State/Boot/PostCode0", 442 "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount", 443 [asyncResp, entryCount, skip, 444 top](const boost::system::error_code& ec, const uint16_t bootCount) { 445 if (ec) 446 { 447 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 448 messages::internalError(asyncResp->res); 449 return; 450 } 451 getPostCodeForBoot(asyncResp, 1, bootCount, entryCount, skip, top); 452 }); 453 } 454 455 inline void handleSystemsLogServicesPostCodesEntriesGet( 456 App& app, const crow::Request& req, 457 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 458 const std::string& systemName) 459 { 460 query_param::QueryCapabilities capabilities = { 461 .canDelegateTop = true, 462 .canDelegateSkip = true, 463 }; 464 query_param::Query delegatedQuery; 465 if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp, 466 delegatedQuery, capabilities)) 467 { 468 return; 469 } 470 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 471 { 472 // Option currently returns no systems. TBD 473 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 474 systemName); 475 return; 476 } 477 478 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 479 { 480 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 481 systemName); 482 return; 483 } 484 asyncResp->res.jsonValue["@odata.type"] = 485 "#LogEntryCollection.LogEntryCollection"; 486 asyncResp->res.jsonValue["@odata.id"] = 487 std::format("/redfish/v1/Systems/{}/LogServices/PostCodes/Entries", 488 BMCWEB_REDFISH_SYSTEM_URI_NAME); 489 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 490 asyncResp->res.jsonValue["Description"] = 491 "Collection of POST Code Log Entries"; 492 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 493 asyncResp->res.jsonValue["Members@odata.count"] = 0; 494 size_t skip = delegatedQuery.skip.value_or(0); 495 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 496 getCurrentBootNumber(asyncResp, skip, top); 497 } 498 499 inline void handleSystemsLogServicesPostCodesEntriesEntryAdditionalDataGet( 500 App& app, const crow::Request& req, 501 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 502 const std::string& systemName, const std::string& postCodeID) 503 { 504 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 505 { 506 return; 507 } 508 if (!http_helpers::isContentTypeAllowed( 509 req.getHeaderValue("Accept"), 510 http_helpers::ContentType::OctetStream, true)) 511 { 512 asyncResp->res.result(boost::beast::http::status::bad_request); 513 return; 514 } 515 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 516 { 517 // Option currently returns no systems. TBD 518 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 519 systemName); 520 return; 521 } 522 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 523 { 524 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 525 systemName); 526 return; 527 } 528 529 uint64_t currentValue = 0; 530 uint16_t index = 0; 531 if (!parsePostCode(postCodeID, currentValue, index)) 532 { 533 messages::resourceNotFound(asyncResp->res, "LogEntry", postCodeID); 534 return; 535 } 536 537 crow::connections::systemBus->async_method_call( 538 [asyncResp, postCodeID, currentValue]( 539 const boost::system::error_code& ec, 540 const std::vector<std::tuple<std::vector<uint8_t>, 541 std::vector<uint8_t>>>& postcodes) { 542 if (ec.value() == EBADR) 543 { 544 messages::resourceNotFound(asyncResp->res, "LogEntry", 545 postCodeID); 546 return; 547 } 548 if (ec) 549 { 550 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 551 messages::internalError(asyncResp->res); 552 return; 553 } 554 555 size_t value = static_cast<size_t>(currentValue) - 1; 556 if (value == std::string::npos || postcodes.size() < currentValue) 557 { 558 BMCWEB_LOG_WARNING("Wrong currentValue value"); 559 messages::resourceNotFound(asyncResp->res, "LogEntry", 560 postCodeID); 561 return; 562 } 563 564 const auto& [tID, c] = postcodes[value]; 565 if (c.empty()) 566 { 567 BMCWEB_LOG_WARNING("No found post code data"); 568 messages::resourceNotFound(asyncResp->res, "LogEntry", 569 postCodeID); 570 return; 571 } 572 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 573 const char* d = reinterpret_cast<const char*>(c.data()); 574 std::string_view strData(d, c.size()); 575 576 asyncResp->res.addHeader(boost::beast::http::field::content_type, 577 "application/octet-stream"); 578 asyncResp->res.addHeader( 579 boost::beast::http::field::content_transfer_encoding, "Base64"); 580 asyncResp->res.write(crow::utility::base64encode(strData)); 581 }, 582 "xyz.openbmc_project.State.Boot.PostCode0", 583 "/xyz/openbmc_project/State/Boot/PostCode0", 584 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodes", index); 585 } 586 587 inline void handleSystemsLogServicesPostCodesEntriesEntryGet( 588 App& app, const crow::Request& req, 589 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 590 const std::string& systemName, const std::string& targetID) 591 { 592 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 593 { 594 return; 595 } 596 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 597 { 598 // Option currently returns no systems. TBD 599 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 600 systemName); 601 return; 602 } 603 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 604 { 605 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 606 systemName); 607 return; 608 } 609 610 getPostCodeForEntry(asyncResp, targetID); 611 } 612 613 inline void requestRoutesSystemsLogServicesPostCode(App& app) 614 { 615 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/") 616 .privileges(redfish::privileges::getLogService) 617 .methods(boost::beast::http::verb::get)(std::bind_front( 618 handleSystemsLogServicesPostCodesGet, std::ref(app))); 619 620 BMCWEB_ROUTE( 621 app, 622 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Actions/LogService.ClearLog/") 623 // The following privilege is correct; we need "SubordinateOverrides" 624 // before we can automate it. 625 .privileges({{"ConfigureComponents"}}) 626 .methods(boost::beast::http::verb::post)(std::bind_front( 627 handleSystemsLogServicesPostCodesPost, std::ref(app))); 628 629 BMCWEB_ROUTE(app, 630 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/") 631 .privileges(redfish::privileges::getLogEntryCollection) 632 .methods(boost::beast::http::verb::get)(std::bind_front( 633 handleSystemsLogServicesPostCodesEntriesGet, std::ref(app))); 634 635 BMCWEB_ROUTE( 636 app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/") 637 .privileges(redfish::privileges::getLogEntry) 638 .methods(boost::beast::http::verb::get)(std::bind_front( 639 handleSystemsLogServicesPostCodesEntriesEntryGet, std::ref(app))); 640 641 BMCWEB_ROUTE( 642 app, 643 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/attachment/") 644 .privileges(redfish::privileges::getLogEntry) 645 .methods(boost::beast::http::verb::get)(std::bind_front( 646 handleSystemsLogServicesPostCodesEntriesEntryAdditionalDataGet, 647 std::ref(app))); 648 } 649 } // namespace redfish 650