1 /* 2 // Copyright (c) 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #pragma once 17 18 #include "filesystem.hpp" 19 #include "node.hpp" 20 21 #include <systemd/sd-journal.h> 22 23 #include <boost/container/flat_map.hpp> 24 #include <boost/utility/string_view.hpp> 25 #include <variant> 26 27 namespace redfish 28 { 29 30 constexpr char const *cpuLogObject = "com.intel.CpuDebugLog"; 31 constexpr char const *cpuLogPath = "/com/intel/CpuDebugLog"; 32 constexpr char const *cpuLogImmediatePath = "/com/intel/CpuDebugLog/Immediate"; 33 constexpr char const *cpuLogInterface = "com.intel.CpuDebugLog"; 34 constexpr char const *cpuLogImmediateInterface = 35 "com.intel.CpuDebugLog.Immediate"; 36 constexpr char const *cpuLogRawPECIInterface = 37 "com.intel.CpuDebugLog.SendRawPeci"; 38 39 namespace fs = std::filesystem; 40 41 static int getJournalMetadata(sd_journal *journal, 42 const boost::string_view &field, 43 boost::string_view &contents) 44 { 45 const char *data = nullptr; 46 size_t length = 0; 47 int ret = 0; 48 // Get the metadata from the requested field of the journal entry 49 ret = sd_journal_get_data(journal, field.data(), (const void **)&data, 50 &length); 51 if (ret < 0) 52 { 53 return ret; 54 } 55 contents = boost::string_view(data, length); 56 // Only use the content after the "=" character. 57 contents.remove_prefix(std::min(contents.find("=") + 1, contents.size())); 58 return ret; 59 } 60 61 static int getJournalMetadata(sd_journal *journal, 62 const boost::string_view &field, const int &base, 63 int &contents) 64 { 65 int ret = 0; 66 boost::string_view metadata; 67 // Get the metadata from the requested field of the journal entry 68 ret = getJournalMetadata(journal, field, metadata); 69 if (ret < 0) 70 { 71 return ret; 72 } 73 contents = strtol(metadata.data(), nullptr, base); 74 return ret; 75 } 76 77 static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp) 78 { 79 int ret = 0; 80 uint64_t timestamp = 0; 81 ret = sd_journal_get_realtime_usec(journal, ×tamp); 82 if (ret < 0) 83 { 84 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 85 << strerror(-ret); 86 return false; 87 } 88 time_t t = 89 static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s 90 struct tm *loctime = localtime(&t); 91 char entryTime[64] = {}; 92 if (NULL != loctime) 93 { 94 strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime); 95 } 96 // Insert the ':' into the timezone 97 boost::string_view t1(entryTime); 98 boost::string_view t2(entryTime); 99 if (t1.size() > 2 && t2.size() > 2) 100 { 101 t1.remove_suffix(2); 102 t2.remove_prefix(t2.size() - 2); 103 } 104 entryTimestamp = t1.to_string() + ":" + t2.to_string(); 105 return true; 106 } 107 108 static bool getSkipParam(crow::Response &res, const crow::Request &req, 109 long &skip) 110 { 111 char *skipParam = req.urlParams.get("$skip"); 112 if (skipParam != nullptr) 113 { 114 char *ptr = nullptr; 115 skip = std::strtol(skipParam, &ptr, 10); 116 if (*skipParam == '\0' || *ptr != '\0') 117 { 118 119 messages::queryParameterValueTypeError(res, std::string(skipParam), 120 "$skip"); 121 return false; 122 } 123 if (skip < 0) 124 { 125 126 messages::queryParameterOutOfRange(res, std::to_string(skip), 127 "$skip", "greater than 0"); 128 return false; 129 } 130 } 131 return true; 132 } 133 134 static constexpr const long maxEntriesPerPage = 1000; 135 static bool getTopParam(crow::Response &res, const crow::Request &req, 136 long &top) 137 { 138 char *topParam = req.urlParams.get("$top"); 139 if (topParam != nullptr) 140 { 141 char *ptr = nullptr; 142 top = std::strtol(topParam, &ptr, 10); 143 if (*topParam == '\0' || *ptr != '\0') 144 { 145 messages::queryParameterValueTypeError(res, std::string(topParam), 146 "$top"); 147 return false; 148 } 149 if (top < 1 || top > maxEntriesPerPage) 150 { 151 152 messages::queryParameterOutOfRange( 153 res, std::to_string(top), "$top", 154 "1-" + std::to_string(maxEntriesPerPage)); 155 return false; 156 } 157 } 158 return true; 159 } 160 161 static bool getUniqueEntryID(sd_journal *journal, std::string &entryID) 162 { 163 int ret = 0; 164 static uint64_t prevTs = 0; 165 static int index = 0; 166 // Get the entry timestamp 167 uint64_t curTs = 0; 168 ret = sd_journal_get_realtime_usec(journal, &curTs); 169 if (ret < 0) 170 { 171 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 172 << strerror(-ret); 173 return false; 174 } 175 // If the timestamp isn't unique, increment the index 176 if (curTs == prevTs) 177 { 178 index++; 179 } 180 else 181 { 182 // Otherwise, reset it 183 index = 0; 184 } 185 // Save the timestamp 186 prevTs = curTs; 187 188 entryID = std::to_string(curTs); 189 if (index > 0) 190 { 191 entryID += "_" + std::to_string(index); 192 } 193 return true; 194 } 195 196 static bool getTimestampFromID(crow::Response &res, const std::string &entryID, 197 uint64_t ×tamp, uint16_t &index) 198 { 199 if (entryID.empty()) 200 { 201 return false; 202 } 203 // Convert the unique ID back to a timestamp to find the entry 204 boost::string_view tsStr(entryID); 205 206 auto underscorePos = tsStr.find("_"); 207 if (underscorePos != tsStr.npos) 208 { 209 // Timestamp has an index 210 tsStr.remove_suffix(tsStr.size() - underscorePos); 211 boost::string_view indexStr(entryID); 212 indexStr.remove_prefix(underscorePos + 1); 213 std::size_t pos; 214 try 215 { 216 index = std::stoul(indexStr.to_string(), &pos); 217 } 218 catch (std::invalid_argument) 219 { 220 messages::resourceMissingAtURI(res, entryID); 221 return false; 222 } 223 catch (std::out_of_range) 224 { 225 messages::resourceMissingAtURI(res, entryID); 226 return false; 227 } 228 if (pos != indexStr.size()) 229 { 230 messages::resourceMissingAtURI(res, entryID); 231 return false; 232 } 233 } 234 // Timestamp has no index 235 std::size_t pos; 236 try 237 { 238 timestamp = std::stoull(tsStr.to_string(), &pos); 239 } 240 catch (std::invalid_argument) 241 { 242 messages::resourceMissingAtURI(res, entryID); 243 return false; 244 } 245 catch (std::out_of_range) 246 { 247 messages::resourceMissingAtURI(res, entryID); 248 return false; 249 } 250 if (pos != tsStr.size()) 251 { 252 messages::resourceMissingAtURI(res, entryID); 253 return false; 254 } 255 return true; 256 } 257 258 class SystemLogServiceCollection : public Node 259 { 260 public: 261 template <typename CrowApp> 262 SystemLogServiceCollection(CrowApp &app) : 263 Node(app, "/redfish/v1/Systems/system/LogServices/") 264 { 265 entityPrivileges = { 266 {boost::beast::http::verb::get, {{"Login"}}}, 267 {boost::beast::http::verb::head, {{"Login"}}}, 268 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 269 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 270 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 271 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 272 } 273 274 private: 275 /** 276 * Functions triggers appropriate requests on DBus 277 */ 278 void doGet(crow::Response &res, const crow::Request &req, 279 const std::vector<std::string> ¶ms) override 280 { 281 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 282 // Collections don't include the static data added by SubRoute because 283 // it has a duplicate entry for members 284 asyncResp->res.jsonValue["@odata.type"] = 285 "#LogServiceCollection.LogServiceCollection"; 286 asyncResp->res.jsonValue["@odata.context"] = 287 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; 288 asyncResp->res.jsonValue["@odata.id"] = 289 "/redfish/v1/Systems/system/LogServices"; 290 asyncResp->res.jsonValue["Name"] = "System Log Services Collection"; 291 asyncResp->res.jsonValue["Description"] = 292 "Collection of LogServices for this Computer System"; 293 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 294 logServiceArray = nlohmann::json::array(); 295 logServiceArray.push_back( 296 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}}); 297 asyncResp->res.jsonValue["Members@odata.count"] = 298 logServiceArray.size(); 299 } 300 }; 301 302 class EventLogService : public Node 303 { 304 public: 305 template <typename CrowApp> 306 EventLogService(CrowApp &app) : 307 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/") 308 { 309 entityPrivileges = { 310 {boost::beast::http::verb::get, {{"Login"}}}, 311 {boost::beast::http::verb::head, {{"Login"}}}, 312 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 313 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 314 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 315 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 316 } 317 318 private: 319 void doGet(crow::Response &res, const crow::Request &req, 320 const std::vector<std::string> ¶ms) override 321 { 322 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 323 324 const std::string &name = params[0]; 325 asyncResp->res.jsonValue["@odata.id"] = 326 "/redfish/v1/Systems/system/LogServices/EventLog"; 327 asyncResp->res.jsonValue["@odata.type"] = 328 "#LogService.v1_1_0.LogService"; 329 asyncResp->res.jsonValue["@odata.context"] = 330 "/redfish/v1/$metadata#LogService.LogService"; 331 asyncResp->res.jsonValue["Name"] = "Event Log Service"; 332 asyncResp->res.jsonValue["Description"] = "System Event Log Service"; 333 asyncResp->res.jsonValue["Id"] = "Event Log"; 334 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 335 asyncResp->res.jsonValue["Entries"] = { 336 {"@odata.id", 337 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}}; 338 } 339 }; 340 341 static int fillEventLogEntryJson(const std::string &bmcLogEntryID, 342 const boost::string_view &messageID, 343 sd_journal *journal, 344 nlohmann::json &bmcLogEntryJson) 345 { 346 // Get the Log Entry contents 347 int ret = 0; 348 349 boost::string_view msg; 350 ret = getJournalMetadata(journal, "MESSAGE", msg); 351 if (ret < 0) 352 { 353 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 354 return 1; 355 } 356 357 // Get the severity from the PRIORITY field 358 int severity = 8; // Default to an invalid priority 359 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 360 if (ret < 0) 361 { 362 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 363 return 1; 364 } 365 366 // Get the MessageArgs from the journal entry by finding all of the 367 // REDFISH_MESSAGE_ARG_x fields 368 const void *data; 369 size_t length; 370 std::vector<std::string> messageArgs; 371 SD_JOURNAL_FOREACH_DATA(journal, data, length) 372 { 373 boost::string_view field(static_cast<const char *>(data), length); 374 if (field.starts_with("REDFISH_MESSAGE_ARG_")) 375 { 376 // Get the Arg number from the field name 377 field.remove_prefix(sizeof("REDFISH_MESSAGE_ARG_") - 1); 378 if (field.empty()) 379 { 380 continue; 381 } 382 int argNum = std::strtoul(field.data(), nullptr, 10); 383 if (argNum == 0) 384 { 385 continue; 386 } 387 // Get the Arg value after the "=" character. 388 field.remove_prefix(std::min(field.find("=") + 1, field.size())); 389 // Make sure we have enough space in messageArgs 390 if (argNum > messageArgs.size()) 391 { 392 messageArgs.resize(argNum); 393 } 394 messageArgs[argNum - 1] = field.to_string(); 395 } 396 } 397 398 // Get the Created time from the timestamp 399 std::string entryTimeStr; 400 if (!getEntryTimestamp(journal, entryTimeStr)) 401 { 402 return 1; 403 } 404 405 // Fill in the log entry with the gathered data 406 bmcLogEntryJson = { 407 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 408 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 409 {"@odata.id", 410 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + 411 bmcLogEntryID}, 412 {"Name", "System Event Log Entry"}, 413 {"Id", bmcLogEntryID}, 414 {"Message", msg}, 415 {"MessageId", messageID}, 416 {"MessageArgs", std::move(messageArgs)}, 417 {"EntryType", "Event"}, 418 {"Severity", 419 severity <= 2 ? "Critical" 420 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""}, 421 {"Created", std::move(entryTimeStr)}}; 422 return 0; 423 } 424 425 class EventLogEntryCollection : public Node 426 { 427 public: 428 template <typename CrowApp> 429 EventLogEntryCollection(CrowApp &app) : 430 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 431 { 432 entityPrivileges = { 433 {boost::beast::http::verb::get, {{"Login"}}}, 434 {boost::beast::http::verb::head, {{"Login"}}}, 435 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 436 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 437 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 438 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 439 } 440 441 private: 442 void doGet(crow::Response &res, const crow::Request &req, 443 const std::vector<std::string> ¶ms) override 444 { 445 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 446 long skip = 0; 447 long top = maxEntriesPerPage; // Show max entries by default 448 if (!getSkipParam(asyncResp->res, req, skip)) 449 { 450 return; 451 } 452 if (!getTopParam(asyncResp->res, req, top)) 453 { 454 return; 455 } 456 // Collections don't include the static data added by SubRoute because 457 // it has a duplicate entry for members 458 asyncResp->res.jsonValue["@odata.type"] = 459 "#LogEntryCollection.LogEntryCollection"; 460 asyncResp->res.jsonValue["@odata.context"] = 461 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 462 asyncResp->res.jsonValue["@odata.id"] = 463 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 464 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 465 asyncResp->res.jsonValue["Description"] = 466 "Collection of System Event Log Entries"; 467 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 468 logEntryArray = nlohmann::json::array(); 469 470 // Go through the journal and create a unique ID for each entry 471 sd_journal *journalTmp = nullptr; 472 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 473 if (ret < 0) 474 { 475 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 476 messages::internalError(asyncResp->res); 477 return; 478 } 479 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 480 journalTmp, sd_journal_close); 481 journalTmp = nullptr; 482 uint64_t entryCount = 0; 483 SD_JOURNAL_FOREACH(journal.get()) 484 { 485 // Look for only journal entries that contain a REDFISH_MESSAGE_ID 486 // field 487 boost::string_view messageID; 488 ret = getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", 489 messageID); 490 if (ret < 0) 491 { 492 continue; 493 } 494 495 entryCount++; 496 // Handle paging using skip (number of entries to skip from the 497 // start) and top (number of entries to display) 498 if (entryCount <= skip || entryCount > skip + top) 499 { 500 continue; 501 } 502 503 std::string idStr; 504 if (!getUniqueEntryID(journal.get(), idStr)) 505 { 506 continue; 507 } 508 509 logEntryArray.push_back({}); 510 nlohmann::json &bmcLogEntry = logEntryArray.back(); 511 if (fillEventLogEntryJson(idStr, messageID, journal.get(), 512 bmcLogEntry) != 0) 513 { 514 messages::internalError(asyncResp->res); 515 return; 516 } 517 } 518 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 519 if (skip + top < entryCount) 520 { 521 asyncResp->res.jsonValue["Members@odata.nextLink"] = 522 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries?$skip=" + 523 std::to_string(skip + top); 524 } 525 } 526 }; 527 528 class EventLogEntry : public Node 529 { 530 public: 531 EventLogEntry(CrowApp &app) : 532 Node(app, 533 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/", 534 std::string()) 535 { 536 entityPrivileges = { 537 {boost::beast::http::verb::get, {{"Login"}}}, 538 {boost::beast::http::verb::head, {{"Login"}}}, 539 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 540 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 541 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 542 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 543 } 544 545 private: 546 void doGet(crow::Response &res, const crow::Request &req, 547 const std::vector<std::string> ¶ms) override 548 { 549 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 550 if (params.size() != 1) 551 { 552 messages::internalError(asyncResp->res); 553 return; 554 } 555 const std::string &entryID = params[0]; 556 // Convert the unique ID back to a timestamp to find the entry 557 uint64_t ts = 0; 558 uint16_t index = 0; 559 if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 560 { 561 return; 562 } 563 564 sd_journal *journalTmp = nullptr; 565 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 566 if (ret < 0) 567 { 568 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 569 messages::internalError(asyncResp->res); 570 return; 571 } 572 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 573 journalTmp, sd_journal_close); 574 journalTmp = nullptr; 575 // Go to the timestamp in the log and move to the entry at the index 576 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 577 for (int i = 0; i <= index; i++) 578 { 579 sd_journal_next(journal.get()); 580 } 581 // Confirm that the entry ID matches what was requested 582 std::string idStr; 583 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID) 584 { 585 messages::resourceMissingAtURI(asyncResp->res, entryID); 586 return; 587 } 588 589 // only use journal entries that contain a REDFISH_MESSAGE_ID 590 // field 591 boost::string_view messageID; 592 ret = 593 getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", messageID); 594 if (ret < 0) 595 { 596 messages::resourceNotFound(asyncResp->res, "LogEntry", "system"); 597 return; 598 } 599 600 if (fillEventLogEntryJson(entryID, messageID, journal.get(), 601 asyncResp->res.jsonValue) != 0) 602 { 603 messages::internalError(asyncResp->res); 604 return; 605 } 606 } 607 }; 608 609 class BMCLogServiceCollection : public Node 610 { 611 public: 612 template <typename CrowApp> 613 BMCLogServiceCollection(CrowApp &app) : 614 Node(app, "/redfish/v1/Managers/bmc/LogServices/") 615 { 616 entityPrivileges = { 617 {boost::beast::http::verb::get, {{"Login"}}}, 618 {boost::beast::http::verb::head, {{"Login"}}}, 619 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 620 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 621 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 622 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 623 } 624 625 private: 626 /** 627 * Functions triggers appropriate requests on DBus 628 */ 629 void doGet(crow::Response &res, const crow::Request &req, 630 const std::vector<std::string> ¶ms) override 631 { 632 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 633 // Collections don't include the static data added by SubRoute because 634 // it has a duplicate entry for members 635 asyncResp->res.jsonValue["@odata.type"] = 636 "#LogServiceCollection.LogServiceCollection"; 637 asyncResp->res.jsonValue["@odata.context"] = 638 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; 639 asyncResp->res.jsonValue["@odata.id"] = 640 "/redfish/v1/Managers/bmc/LogServices"; 641 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; 642 asyncResp->res.jsonValue["Description"] = 643 "Collection of LogServices for this Manager"; 644 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 645 logServiceArray = nlohmann::json::array(); 646 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 647 logServiceArray.push_back( 648 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}}); 649 #endif 650 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG 651 logServiceArray.push_back( 652 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/CpuLog"}}); 653 #endif 654 asyncResp->res.jsonValue["Members@odata.count"] = 655 logServiceArray.size(); 656 } 657 }; 658 659 class BMCJournalLogService : public Node 660 { 661 public: 662 template <typename CrowApp> 663 BMCJournalLogService(CrowApp &app) : 664 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 665 { 666 entityPrivileges = { 667 {boost::beast::http::verb::get, {{"Login"}}}, 668 {boost::beast::http::verb::head, {{"Login"}}}, 669 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 670 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 671 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 672 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 673 } 674 675 private: 676 void doGet(crow::Response &res, const crow::Request &req, 677 const std::vector<std::string> ¶ms) override 678 { 679 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 680 asyncResp->res.jsonValue["@odata.type"] = 681 "#LogService.v1_1_0.LogService"; 682 asyncResp->res.jsonValue["@odata.id"] = 683 "/redfish/v1/Managers/bmc/LogServices/Journal"; 684 asyncResp->res.jsonValue["@odata.context"] = 685 "/redfish/v1/$metadata#LogService.LogService"; 686 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 687 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 688 asyncResp->res.jsonValue["Id"] = "BMC Journal"; 689 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 690 } 691 }; 692 693 static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID, 694 sd_journal *journal, 695 nlohmann::json &bmcJournalLogEntryJson) 696 { 697 // Get the Log Entry contents 698 int ret = 0; 699 700 boost::string_view msg; 701 ret = getJournalMetadata(journal, "MESSAGE", msg); 702 if (ret < 0) 703 { 704 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 705 return 1; 706 } 707 708 // Get the severity from the PRIORITY field 709 int severity = 8; // Default to an invalid priority 710 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 711 if (ret < 0) 712 { 713 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 714 return 1; 715 } 716 717 // Get the Created time from the timestamp 718 std::string entryTimeStr; 719 if (!getEntryTimestamp(journal, entryTimeStr)) 720 { 721 return 1; 722 } 723 724 // Fill in the log entry with the gathered data 725 bmcJournalLogEntryJson = { 726 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 727 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 728 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + 729 bmcJournalLogEntryID}, 730 {"Name", "BMC Journal Entry"}, 731 {"Id", bmcJournalLogEntryID}, 732 {"Message", msg}, 733 {"EntryType", "Oem"}, 734 {"Severity", 735 severity <= 2 ? "Critical" 736 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""}, 737 {"OemRecordFormat", "Intel BMC Journal Entry"}, 738 {"Created", std::move(entryTimeStr)}}; 739 return 0; 740 } 741 742 class BMCJournalLogEntryCollection : public Node 743 { 744 public: 745 template <typename CrowApp> 746 BMCJournalLogEntryCollection(CrowApp &app) : 747 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 748 { 749 entityPrivileges = { 750 {boost::beast::http::verb::get, {{"Login"}}}, 751 {boost::beast::http::verb::head, {{"Login"}}}, 752 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 753 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 754 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 755 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 756 } 757 758 private: 759 void doGet(crow::Response &res, const crow::Request &req, 760 const std::vector<std::string> ¶ms) override 761 { 762 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 763 static constexpr const long maxEntriesPerPage = 1000; 764 long skip = 0; 765 long top = maxEntriesPerPage; // Show max entries by default 766 if (!getSkipParam(asyncResp->res, req, skip)) 767 { 768 return; 769 } 770 if (!getTopParam(asyncResp->res, req, top)) 771 { 772 return; 773 } 774 // Collections don't include the static data added by SubRoute because 775 // it has a duplicate entry for members 776 asyncResp->res.jsonValue["@odata.type"] = 777 "#LogEntryCollection.LogEntryCollection"; 778 asyncResp->res.jsonValue["@odata.id"] = 779 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 780 asyncResp->res.jsonValue["@odata.context"] = 781 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 782 asyncResp->res.jsonValue["@odata.id"] = 783 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 784 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 785 asyncResp->res.jsonValue["Description"] = 786 "Collection of BMC Journal Entries"; 787 asyncResp->res.jsonValue["@odata.id"] = 788 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries"; 789 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 790 logEntryArray = nlohmann::json::array(); 791 792 // Go through the journal and use the timestamp to create a unique ID 793 // for each entry 794 sd_journal *journalTmp = nullptr; 795 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 796 if (ret < 0) 797 { 798 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 799 messages::internalError(asyncResp->res); 800 return; 801 } 802 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 803 journalTmp, sd_journal_close); 804 journalTmp = nullptr; 805 uint64_t entryCount = 0; 806 SD_JOURNAL_FOREACH(journal.get()) 807 { 808 entryCount++; 809 // Handle paging using skip (number of entries to skip from the 810 // start) and top (number of entries to display) 811 if (entryCount <= skip || entryCount > skip + top) 812 { 813 continue; 814 } 815 816 std::string idStr; 817 if (!getUniqueEntryID(journal.get(), idStr)) 818 { 819 continue; 820 } 821 822 logEntryArray.push_back({}); 823 nlohmann::json &bmcJournalLogEntry = logEntryArray.back(); 824 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 825 bmcJournalLogEntry) != 0) 826 { 827 messages::internalError(asyncResp->res); 828 return; 829 } 830 } 831 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 832 if (skip + top < entryCount) 833 { 834 asyncResp->res.jsonValue["Members@odata.nextLink"] = 835 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 836 std::to_string(skip + top); 837 } 838 } 839 }; 840 841 class BMCJournalLogEntry : public Node 842 { 843 public: 844 BMCJournalLogEntry(CrowApp &app) : 845 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/", 846 std::string()) 847 { 848 entityPrivileges = { 849 {boost::beast::http::verb::get, {{"Login"}}}, 850 {boost::beast::http::verb::head, {{"Login"}}}, 851 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 852 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 853 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 854 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 855 } 856 857 private: 858 void doGet(crow::Response &res, const crow::Request &req, 859 const std::vector<std::string> ¶ms) override 860 { 861 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 862 if (params.size() != 1) 863 { 864 messages::internalError(asyncResp->res); 865 return; 866 } 867 const std::string &entryID = params[0]; 868 // Convert the unique ID back to a timestamp to find the entry 869 uint64_t ts = 0; 870 uint16_t index = 0; 871 if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 872 { 873 return; 874 } 875 876 sd_journal *journalTmp = nullptr; 877 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 878 if (ret < 0) 879 { 880 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 881 messages::internalError(asyncResp->res); 882 return; 883 } 884 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 885 journalTmp, sd_journal_close); 886 journalTmp = nullptr; 887 // Go to the timestamp in the log and move to the entry at the index 888 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 889 for (int i = 0; i <= index; i++) 890 { 891 sd_journal_next(journal.get()); 892 } 893 // Confirm that the entry ID matches what was requested 894 std::string idStr; 895 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID) 896 { 897 messages::resourceMissingAtURI(asyncResp->res, entryID); 898 return; 899 } 900 901 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 902 asyncResp->res.jsonValue) != 0) 903 { 904 messages::internalError(asyncResp->res); 905 return; 906 } 907 } 908 }; 909 910 class CPULogService : public Node 911 { 912 public: 913 template <typename CrowApp> 914 CPULogService(CrowApp &app) : 915 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/") 916 { 917 entityPrivileges = { 918 {boost::beast::http::verb::get, {{"Login"}}}, 919 {boost::beast::http::verb::head, {{"Login"}}}, 920 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 921 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 922 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 923 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 924 } 925 926 private: 927 /** 928 * Functions triggers appropriate requests on DBus 929 */ 930 void doGet(crow::Response &res, const crow::Request &req, 931 const std::vector<std::string> ¶ms) override 932 { 933 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 934 // Copy over the static data to include the entries added by SubRoute 935 asyncResp->res.jsonValue["@odata.id"] = 936 "/redfish/v1/Managers/bmc/LogServices/CpuLog"; 937 asyncResp->res.jsonValue["@odata.type"] = 938 "#LogService.v1_1_0.LogService"; 939 asyncResp->res.jsonValue["@odata.context"] = 940 "/redfish/v1/$metadata#LogService.LogService"; 941 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service"; 942 asyncResp->res.jsonValue["Description"] = "CPU Log Service"; 943 asyncResp->res.jsonValue["Id"] = "CPU Log"; 944 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 945 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 946 asyncResp->res.jsonValue["Actions"] = { 947 {"Oem", 948 {{"#CpuLog.Immediate", 949 {{"target", "/redfish/v1/Managers/bmc/LogServices/CpuLog/" 950 "Actions/Oem/CpuLog.Immediate"}}}}}}; 951 952 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI 953 asyncResp->res.jsonValue["Actions"]["Oem"].push_back( 954 {"#CpuLog.SendRawPeci", 955 {{"target", "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/" 956 "Oem/CpuLog.SendRawPeci"}}}); 957 #endif 958 } 959 }; 960 961 class CPULogEntryCollection : public Node 962 { 963 public: 964 template <typename CrowApp> 965 CPULogEntryCollection(CrowApp &app) : 966 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/") 967 { 968 entityPrivileges = { 969 {boost::beast::http::verb::get, {{"Login"}}}, 970 {boost::beast::http::verb::head, {{"Login"}}}, 971 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 972 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 973 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 974 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 975 } 976 977 private: 978 /** 979 * Functions triggers appropriate requests on DBus 980 */ 981 void doGet(crow::Response &res, const crow::Request &req, 982 const std::vector<std::string> ¶ms) override 983 { 984 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 985 // Collections don't include the static data added by SubRoute because 986 // it has a duplicate entry for members 987 auto getLogEntriesCallback = [asyncResp]( 988 const boost::system::error_code ec, 989 const std::vector<std::string> &resp) { 990 if (ec) 991 { 992 if (ec.value() != 993 boost::system::errc::no_such_file_or_directory) 994 { 995 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 996 << ec.message(); 997 messages::internalError(asyncResp->res); 998 return; 999 } 1000 } 1001 asyncResp->res.jsonValue["@odata.type"] = 1002 "#LogEntryCollection.LogEntryCollection"; 1003 asyncResp->res.jsonValue["@odata.id"] = 1004 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries"; 1005 asyncResp->res.jsonValue["@odata.context"] = 1006 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 1007 asyncResp->res.jsonValue["@odata.id"] = 1008 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries"; 1009 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries"; 1010 asyncResp->res.jsonValue["Description"] = 1011 "Collection of CPU Log Entries"; 1012 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1013 logEntryArray = nlohmann::json::array(); 1014 for (const std::string &objpath : resp) 1015 { 1016 // Don't list the immediate log 1017 if (objpath.compare(cpuLogImmediatePath) == 0) 1018 { 1019 continue; 1020 } 1021 std::size_t lastPos = objpath.rfind("/"); 1022 if (lastPos != std::string::npos) 1023 { 1024 logEntryArray.push_back( 1025 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/" 1026 "CpuLog/Entries/" + 1027 objpath.substr(lastPos + 1)}}); 1028 } 1029 } 1030 asyncResp->res.jsonValue["Members@odata.count"] = 1031 logEntryArray.size(); 1032 }; 1033 crow::connections::systemBus->async_method_call( 1034 std::move(getLogEntriesCallback), 1035 "xyz.openbmc_project.ObjectMapper", 1036 "/xyz/openbmc_project/object_mapper", 1037 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 1038 std::array<const char *, 1>{cpuLogInterface}); 1039 } 1040 }; 1041 1042 std::string getLogCreatedTime(const nlohmann::json &cpuLog) 1043 { 1044 nlohmann::json::const_iterator metaIt = cpuLog.find("metadata"); 1045 if (metaIt != cpuLog.end()) 1046 { 1047 nlohmann::json::const_iterator tsIt = metaIt->find("timestamp"); 1048 if (tsIt != metaIt->end()) 1049 { 1050 const std::string *logTime = tsIt->get_ptr<const std::string *>(); 1051 if (logTime != nullptr) 1052 { 1053 return *logTime; 1054 } 1055 } 1056 } 1057 BMCWEB_LOG_DEBUG << "failed to find log timestamp"; 1058 1059 return std::string(); 1060 } 1061 1062 class CPULogEntry : public Node 1063 { 1064 public: 1065 CPULogEntry(CrowApp &app) : 1066 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/", 1067 std::string()) 1068 { 1069 entityPrivileges = { 1070 {boost::beast::http::verb::get, {{"Login"}}}, 1071 {boost::beast::http::verb::head, {{"Login"}}}, 1072 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1073 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1074 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1075 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1076 } 1077 1078 private: 1079 void doGet(crow::Response &res, const crow::Request &req, 1080 const std::vector<std::string> ¶ms) override 1081 { 1082 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1083 if (params.size() != 1) 1084 { 1085 messages::internalError(asyncResp->res); 1086 return; 1087 } 1088 const uint8_t logId = std::atoi(params[0].c_str()); 1089 auto getStoredLogCallback = [asyncResp, logId]( 1090 const boost::system::error_code ec, 1091 const std::variant<std::string> &resp) { 1092 if (ec) 1093 { 1094 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 1095 messages::internalError(asyncResp->res); 1096 return; 1097 } 1098 const std::string *log = std::get_if<std::string>(&resp); 1099 if (log == nullptr) 1100 { 1101 messages::internalError(asyncResp->res); 1102 return; 1103 } 1104 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 1105 if (j.is_discarded()) 1106 { 1107 messages::internalError(asyncResp->res); 1108 return; 1109 } 1110 std::string t = getLogCreatedTime(j); 1111 asyncResp->res.jsonValue = { 1112 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 1113 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1114 {"@odata.id", 1115 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" + 1116 std::to_string(logId)}, 1117 {"Name", "CPU Debug Log"}, 1118 {"Id", logId}, 1119 {"EntryType", "Oem"}, 1120 {"OemRecordFormat", "Intel CPU Log"}, 1121 {"Oem", {{"Intel", std::move(j)}}}, 1122 {"Created", std::move(t)}}; 1123 }; 1124 crow::connections::systemBus->async_method_call( 1125 std::move(getStoredLogCallback), cpuLogObject, 1126 cpuLogPath + std::string("/") + std::to_string(logId), 1127 "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log"); 1128 } 1129 }; 1130 1131 class ImmediateCPULog : public Node 1132 { 1133 public: 1134 ImmediateCPULog(CrowApp &app) : 1135 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 1136 "CpuLog.Immediate/") 1137 { 1138 entityPrivileges = { 1139 {boost::beast::http::verb::get, {{"Login"}}}, 1140 {boost::beast::http::verb::head, {{"Login"}}}, 1141 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1142 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1143 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1144 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1145 } 1146 1147 private: 1148 void doPost(crow::Response &res, const crow::Request &req, 1149 const std::vector<std::string> ¶ms) override 1150 { 1151 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1152 static std::unique_ptr<sdbusplus::bus::match::match> 1153 immediateLogMatcher; 1154 1155 // Only allow one Immediate Log request at a time 1156 if (immediateLogMatcher != nullptr) 1157 { 1158 asyncResp->res.addHeader("Retry-After", "30"); 1159 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 1160 return; 1161 } 1162 // Make this static so it survives outside this method 1163 static boost::asio::deadline_timer timeout(*req.ioService); 1164 1165 timeout.expires_from_now(boost::posix_time::seconds(30)); 1166 timeout.async_wait([asyncResp](const boost::system::error_code &ec) { 1167 immediateLogMatcher = nullptr; 1168 if (ec) 1169 { 1170 // operation_aborted is expected if timer is canceled before 1171 // completion. 1172 if (ec != boost::asio::error::operation_aborted) 1173 { 1174 BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 1175 } 1176 return; 1177 } 1178 BMCWEB_LOG_ERROR << "Timed out waiting for immediate log"; 1179 1180 messages::internalError(asyncResp->res); 1181 }); 1182 1183 auto immediateLogMatcherCallback = [asyncResp]( 1184 sdbusplus::message::message &m) { 1185 BMCWEB_LOG_DEBUG << "Immediate log available match fired"; 1186 boost::system::error_code ec; 1187 timeout.cancel(ec); 1188 if (ec) 1189 { 1190 BMCWEB_LOG_ERROR << "error canceling timer " << ec; 1191 } 1192 sdbusplus::message::object_path objPath; 1193 boost::container::flat_map< 1194 std::string, boost::container::flat_map< 1195 std::string, std::variant<std::string>>> 1196 interfacesAdded; 1197 m.read(objPath, interfacesAdded); 1198 const std::string *log = std::get_if<std::string>( 1199 &interfacesAdded[cpuLogInterface]["Log"]); 1200 if (log == nullptr) 1201 { 1202 messages::internalError(asyncResp->res); 1203 // Careful with immediateLogMatcher. It is a unique_ptr to the 1204 // match object inside which this lambda is executing. Once it 1205 // is set to nullptr, the match object will be destroyed and the 1206 // lambda will lose its context, including res, so it needs to 1207 // be the last thing done. 1208 immediateLogMatcher = nullptr; 1209 return; 1210 } 1211 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 1212 if (j.is_discarded()) 1213 { 1214 messages::internalError(asyncResp->res); 1215 // Careful with immediateLogMatcher. It is a unique_ptr to the 1216 // match object inside which this lambda is executing. Once it 1217 // is set to nullptr, the match object will be destroyed and the 1218 // lambda will lose its context, including res, so it needs to 1219 // be the last thing done. 1220 immediateLogMatcher = nullptr; 1221 return; 1222 } 1223 std::string t = getLogCreatedTime(j); 1224 asyncResp->res.jsonValue = { 1225 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 1226 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1227 {"Name", "CPU Debug Log"}, 1228 {"EntryType", "Oem"}, 1229 {"OemRecordFormat", "Intel CPU Log"}, 1230 {"Oem", {{"Intel", std::move(j)}}}, 1231 {"Created", std::move(t)}}; 1232 // Careful with immediateLogMatcher. It is a unique_ptr to the 1233 // match object inside which this lambda is executing. Once it is 1234 // set to nullptr, the match object will be destroyed and the lambda 1235 // will lose its context, including res, so it needs to be the last 1236 // thing done. 1237 immediateLogMatcher = nullptr; 1238 }; 1239 immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>( 1240 *crow::connections::systemBus, 1241 sdbusplus::bus::match::rules::interfacesAdded() + 1242 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath), 1243 std::move(immediateLogMatcherCallback)); 1244 1245 auto generateImmediateLogCallback = 1246 [asyncResp](const boost::system::error_code ec, 1247 const std::string &resp) { 1248 if (ec) 1249 { 1250 if (ec.value() == 1251 boost::system::errc::operation_not_supported) 1252 { 1253 messages::resourceInStandby(asyncResp->res); 1254 } 1255 else 1256 { 1257 messages::internalError(asyncResp->res); 1258 } 1259 boost::system::error_code timeoutec; 1260 timeout.cancel(timeoutec); 1261 if (timeoutec) 1262 { 1263 BMCWEB_LOG_ERROR << "error canceling timer " 1264 << timeoutec; 1265 } 1266 immediateLogMatcher = nullptr; 1267 return; 1268 } 1269 }; 1270 crow::connections::systemBus->async_method_call( 1271 std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath, 1272 cpuLogImmediateInterface, "GenerateImmediateLog"); 1273 } 1274 }; 1275 1276 class SendRawPECI : public Node 1277 { 1278 public: 1279 SendRawPECI(CrowApp &app) : 1280 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 1281 "CpuLog.SendRawPeci/") 1282 { 1283 entityPrivileges = { 1284 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 1285 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 1286 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1287 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1288 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1289 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1290 } 1291 1292 private: 1293 void doPost(crow::Response &res, const crow::Request &req, 1294 const std::vector<std::string> ¶ms) override 1295 { 1296 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1297 uint8_t clientAddress = 0; 1298 uint8_t readLength = 0; 1299 std::vector<uint8_t> peciCommand; 1300 if (!json_util::readJson(req, res, "ClientAddress", clientAddress, 1301 "ReadLength", readLength, "PECICommand", 1302 peciCommand)) 1303 { 1304 return; 1305 } 1306 1307 // Callback to return the Raw PECI response 1308 auto sendRawPECICallback = 1309 [asyncResp](const boost::system::error_code ec, 1310 const std::vector<uint8_t> &resp) { 1311 if (ec) 1312 { 1313 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: " 1314 << ec.message(); 1315 messages::internalError(asyncResp->res); 1316 return; 1317 } 1318 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"}, 1319 {"PECIResponse", resp}}; 1320 }; 1321 // Call the SendRawPECI command with the provided data 1322 crow::connections::systemBus->async_method_call( 1323 std::move(sendRawPECICallback), cpuLogObject, cpuLogPath, 1324 cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength, 1325 peciCommand); 1326 } 1327 }; 1328 1329 } // namespace redfish 1330