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_utility.hpp" 10 #include "error_messages.hpp" 11 #include "generated/enums/log_service.hpp" 12 #include "http_request.hpp" 13 #include "http_utility.hpp" 14 #include "logging.hpp" 15 #include "query.hpp" 16 #include "registries.hpp" 17 #include "registries/privilege_registry.hpp" 18 #include "str_utility.hpp" 19 #include "utility.hpp" 20 #include "utils/hex_utils.hpp" 21 #include "utils/query_param.hpp" 22 #include "utils/time_utils.hpp" 23 24 #include <asm-generic/errno.h> 25 26 #include <boost/beast/http/field.hpp> 27 #include <boost/beast/http/status.hpp> 28 #include <boost/beast/http/verb.hpp> 29 #include <boost/container/flat_map.hpp> 30 #include <boost/url/format.hpp> 31 32 #include <algorithm> 33 #include <array> 34 #include <charconv> 35 #include <cstddef> 36 #include <cstdint> 37 #include <format> 38 #include <functional> 39 #include <iomanip> 40 #include <ios> 41 #include <memory> 42 #include <sstream> 43 #include <string> 44 #include <string_view> 45 #include <system_error> 46 #include <tuple> 47 #include <utility> 48 #include <vector> 49 50 namespace redfish 51 { 52 53 inline void handleSystemsLogServicesPostCodesGet( 54 App& app, const crow::Request& req, 55 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 56 const std::string& systemName) 57 { 58 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 59 { 60 return; 61 } 62 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 63 { 64 // Option currently returns no systems. TBD 65 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 66 systemName); 67 return; 68 } 69 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 70 { 71 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 72 systemName); 73 return; 74 } 75 asyncResp->res.jsonValue["@odata.id"] = 76 std::format("/redfish/v1/Systems/{}/LogServices/PostCodes", 77 BMCWEB_REDFISH_SYSTEM_URI_NAME); 78 asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService"; 79 asyncResp->res.jsonValue["Name"] = "POST Code Log Service"; 80 asyncResp->res.jsonValue["Description"] = "POST Code Log Service"; 81 asyncResp->res.jsonValue["Id"] = "PostCodes"; 82 asyncResp->res.jsonValue["OverWritePolicy"] = 83 log_service::OverWritePolicy::WrapsWhenFull; 84 asyncResp->res.jsonValue["Entries"]["@odata.id"] = 85 std::format("/redfish/v1/Systems/{}/LogServices/PostCodes/Entries", 86 BMCWEB_REDFISH_SYSTEM_URI_NAME); 87 88 std::pair<std::string, std::string> redfishDateTimeOffset = 89 redfish::time_utils::getDateTimeOffsetNow(); 90 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 91 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 92 redfishDateTimeOffset.second; 93 94 asyncResp->res 95 .jsonValue["Actions"]["#LogService.ClearLog"]["target"] = std::format( 96 "/redfish/v1/Systems/{}/LogServices/PostCodes/Actions/LogService.ClearLog", 97 BMCWEB_REDFISH_SYSTEM_URI_NAME); 98 } 99 100 inline void handleSystemsLogServicesPostCodesPost( 101 App& app, const crow::Request& req, 102 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 103 const std::string& systemName) 104 { 105 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 106 { 107 return; 108 } 109 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 110 { 111 // Option currently returns no systems. TBD 112 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 113 systemName); 114 return; 115 } 116 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 117 { 118 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 119 systemName); 120 return; 121 } 122 BMCWEB_LOG_DEBUG("Do delete all postcodes entries."); 123 124 // Make call to post-code service to request clear all 125 dbus::utility::async_method_call( 126 asyncResp, 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 getPostCodeForEntry( 325 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 dbus::utility::async_method_call( 345 asyncResp, 346 [asyncResp, entryId, bootIndex, 347 codeIndex](const boost::system::error_code& ec, 348 const boost::container::flat_map< 349 uint64_t, std::tuple<std::vector<uint8_t>, 350 std::vector<uint8_t>>>& postcode) { 351 if (ec) 352 { 353 BMCWEB_LOG_DEBUG("DBUS POST CODE PostCode response error"); 354 messages::internalError(asyncResp->res); 355 return; 356 } 357 358 if (postcode.empty()) 359 { 360 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 361 return; 362 } 363 364 if (!fillPostCodeEntry(asyncResp, postcode, bootIndex, codeIndex)) 365 { 366 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 367 return; 368 } 369 }, 370 "xyz.openbmc_project.State.Boot.PostCode0", 371 "/xyz/openbmc_project/State/Boot/PostCode0", 372 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 373 bootIndex); 374 } 375 376 inline void getPostCodeForBoot( 377 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 378 const uint16_t bootIndex, const uint16_t bootCount, 379 const uint64_t entryCount, size_t skip, size_t top) 380 { 381 dbus::utility::async_method_call( 382 asyncResp, 383 [asyncResp, bootIndex, bootCount, entryCount, skip, 384 top](const boost::system::error_code& ec, 385 const boost::container::flat_map< 386 uint64_t, std::tuple<std::vector<uint8_t>, 387 std::vector<uint8_t>>>& postcode) { 388 if (ec) 389 { 390 BMCWEB_LOG_DEBUG("DBUS POST CODE PostCode response error"); 391 messages::internalError(asyncResp->res); 392 return; 393 } 394 395 uint64_t endCount = entryCount; 396 if (!postcode.empty()) 397 { 398 endCount = entryCount + postcode.size(); 399 if (skip < endCount && (top + skip) > entryCount) 400 { 401 uint64_t thisBootSkip = 402 std::max(static_cast<uint64_t>(skip), entryCount) - 403 entryCount; 404 uint64_t thisBootTop = 405 std::min(static_cast<uint64_t>(top + skip), endCount) - 406 entryCount; 407 408 fillPostCodeEntry(asyncResp, postcode, bootIndex, 0, 409 thisBootSkip, thisBootTop); 410 } 411 asyncResp->res.jsonValue["Members@odata.count"] = endCount; 412 } 413 414 // continue to previous bootIndex 415 if (bootIndex < bootCount) 416 { 417 getPostCodeForBoot(asyncResp, 418 static_cast<uint16_t>(bootIndex + 1), 419 bootCount, endCount, skip, top); 420 } 421 else if (skip + top < endCount) 422 { 423 asyncResp->res.jsonValue["Members@odata.nextLink"] = 424 std::format( 425 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries?$skip=", 426 BMCWEB_REDFISH_SYSTEM_URI_NAME) + 427 std::to_string(skip + top); 428 } 429 }, 430 "xyz.openbmc_project.State.Boot.PostCode0", 431 "/xyz/openbmc_project/State/Boot/PostCode0", 432 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 433 bootIndex); 434 } 435 436 inline void getCurrentBootNumber( 437 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, size_t skip, 438 size_t top) 439 { 440 uint64_t entryCount = 0; 441 dbus::utility::getProperty<uint16_t>( 442 "xyz.openbmc_project.State.Boot.PostCode0", 443 "/xyz/openbmc_project/State/Boot/PostCode0", 444 "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount", 445 [asyncResp, entryCount, skip, 446 top](const boost::system::error_code& ec, const uint16_t bootCount) { 447 if (ec) 448 { 449 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 450 messages::internalError(asyncResp->res); 451 return; 452 } 453 getPostCodeForBoot(asyncResp, 1, bootCount, entryCount, skip, top); 454 }); 455 } 456 457 inline void handleSystemsLogServicesPostCodesEntriesGet( 458 App& app, const crow::Request& req, 459 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 460 const std::string& systemName) 461 { 462 query_param::QueryCapabilities capabilities = { 463 .canDelegateTop = true, 464 .canDelegateSkip = true, 465 }; 466 query_param::Query delegatedQuery; 467 if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp, 468 delegatedQuery, capabilities)) 469 { 470 return; 471 } 472 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 473 { 474 // Option currently returns no systems. TBD 475 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 476 systemName); 477 return; 478 } 479 480 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 481 { 482 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 483 systemName); 484 return; 485 } 486 asyncResp->res.jsonValue["@odata.type"] = 487 "#LogEntryCollection.LogEntryCollection"; 488 asyncResp->res.jsonValue["@odata.id"] = 489 std::format("/redfish/v1/Systems/{}/LogServices/PostCodes/Entries", 490 BMCWEB_REDFISH_SYSTEM_URI_NAME); 491 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 492 asyncResp->res.jsonValue["Description"] = 493 "Collection of POST Code Log Entries"; 494 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 495 asyncResp->res.jsonValue["Members@odata.count"] = 0; 496 size_t skip = delegatedQuery.skip.value_or(0); 497 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 498 getCurrentBootNumber(asyncResp, skip, top); 499 } 500 501 inline void handleSystemsLogServicesPostCodesEntriesEntryAdditionalDataGet( 502 App& app, const crow::Request& req, 503 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 504 const std::string& systemName, const std::string& postCodeID) 505 { 506 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 507 { 508 return; 509 } 510 if (!http_helpers::isContentTypeAllowed( 511 req.getHeaderValue("Accept"), 512 http_helpers::ContentType::OctetStream, true)) 513 { 514 asyncResp->res.result(boost::beast::http::status::bad_request); 515 return; 516 } 517 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 518 { 519 // Option currently returns no systems. TBD 520 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 521 systemName); 522 return; 523 } 524 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 525 { 526 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 527 systemName); 528 return; 529 } 530 531 uint64_t currentValue = 0; 532 uint16_t index = 0; 533 if (!parsePostCode(postCodeID, currentValue, index)) 534 { 535 messages::resourceNotFound(asyncResp->res, "LogEntry", postCodeID); 536 return; 537 } 538 539 dbus::utility::async_method_call( 540 asyncResp, 541 [asyncResp, postCodeID, currentValue]( 542 const boost::system::error_code& ec, 543 const std::vector<std::tuple<std::vector<uint8_t>, 544 std::vector<uint8_t>>>& postcodes) { 545 if (ec.value() == EBADR) 546 { 547 messages::resourceNotFound(asyncResp->res, "LogEntry", 548 postCodeID); 549 return; 550 } 551 if (ec) 552 { 553 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 554 messages::internalError(asyncResp->res); 555 return; 556 } 557 558 size_t value = static_cast<size_t>(currentValue) - 1; 559 if (value == std::string::npos || postcodes.size() < currentValue) 560 { 561 BMCWEB_LOG_WARNING("Wrong currentValue value"); 562 messages::resourceNotFound(asyncResp->res, "LogEntry", 563 postCodeID); 564 return; 565 } 566 567 const auto& [tID, c] = postcodes[value]; 568 if (c.empty()) 569 { 570 BMCWEB_LOG_WARNING("No found post code data"); 571 messages::resourceNotFound(asyncResp->res, "LogEntry", 572 postCodeID); 573 return; 574 } 575 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 576 const char* d = reinterpret_cast<const char*>(c.data()); 577 std::string_view strData(d, c.size()); 578 579 asyncResp->res.addHeader(boost::beast::http::field::content_type, 580 "application/octet-stream"); 581 asyncResp->res.addHeader( 582 boost::beast::http::field::content_transfer_encoding, "Base64"); 583 asyncResp->res.write(crow::utility::base64encode(strData)); 584 }, 585 "xyz.openbmc_project.State.Boot.PostCode0", 586 "/xyz/openbmc_project/State/Boot/PostCode0", 587 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodes", index); 588 } 589 590 inline void handleSystemsLogServicesPostCodesEntriesEntryGet( 591 App& app, const crow::Request& req, 592 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 593 const std::string& systemName, const std::string& targetID) 594 { 595 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 596 { 597 return; 598 } 599 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 600 { 601 // Option currently returns no systems. TBD 602 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 603 systemName); 604 return; 605 } 606 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 607 { 608 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 609 systemName); 610 return; 611 } 612 613 getPostCodeForEntry(asyncResp, targetID); 614 } 615 616 inline void requestRoutesSystemsLogServicesPostCode(App& app) 617 { 618 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/") 619 .privileges(redfish::privileges::getLogService) 620 .methods(boost::beast::http::verb::get)(std::bind_front( 621 handleSystemsLogServicesPostCodesGet, std::ref(app))); 622 623 BMCWEB_ROUTE( 624 app, 625 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Actions/LogService.ClearLog/") 626 .privileges(redfish::privileges:: 627 postLogServiceSubOverComputerSystemLogServiceCollection) 628 .methods(boost::beast::http::verb::post)(std::bind_front( 629 handleSystemsLogServicesPostCodesPost, std::ref(app))); 630 631 BMCWEB_ROUTE(app, 632 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/") 633 .privileges(redfish::privileges::getLogEntryCollection) 634 .methods(boost::beast::http::verb::get)(std::bind_front( 635 handleSystemsLogServicesPostCodesEntriesGet, std::ref(app))); 636 637 BMCWEB_ROUTE( 638 app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/") 639 .privileges(redfish::privileges::getLogEntry) 640 .methods(boost::beast::http::verb::get)(std::bind_front( 641 handleSystemsLogServicesPostCodesEntriesEntryGet, std::ref(app))); 642 643 BMCWEB_ROUTE( 644 app, 645 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/attachment/") 646 .privileges(redfish::privileges::getLogEntry) 647 .methods(boost::beast::http::verb::get)(std::bind_front( 648 handleSystemsLogServicesPostCodesEntriesEntryAdditionalDataGet, 649 std::ref(app))); 650 } 651 } // namespace redfish 652