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 std::string_view &field, 43 std::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(), 50 reinterpret_cast<const void **>(&data), &length); 51 if (ret < 0) 52 { 53 return ret; 54 } 55 contents = std::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 std::string_view &field, const int &base, 63 int &contents) 64 { 65 int ret = 0; 66 std::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 = static_cast<int>(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 std::string_view t1(entryTime); 98 std::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 = std::string(t1) + ":" + std::string(t2); 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 std::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 std::string_view indexStr(entryID); 212 indexStr.remove_prefix(underscorePos + 1); 213 std::size_t pos; 214 try 215 { 216 index = std::stoul(std::string(indexStr), &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(std::string(tsStr), &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 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG 298 logServiceArray.push_back( 299 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/CpuLog"}}); 300 #endif 301 asyncResp->res.jsonValue["Members@odata.count"] = 302 logServiceArray.size(); 303 } 304 }; 305 306 class EventLogService : public Node 307 { 308 public: 309 template <typename CrowApp> 310 EventLogService(CrowApp &app) : 311 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/") 312 { 313 entityPrivileges = { 314 {boost::beast::http::verb::get, {{"Login"}}}, 315 {boost::beast::http::verb::head, {{"Login"}}}, 316 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 317 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 318 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 319 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 320 } 321 322 private: 323 void doGet(crow::Response &res, const crow::Request &req, 324 const std::vector<std::string> ¶ms) override 325 { 326 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 327 328 asyncResp->res.jsonValue["@odata.id"] = 329 "/redfish/v1/Systems/system/LogServices/EventLog"; 330 asyncResp->res.jsonValue["@odata.type"] = 331 "#LogService.v1_1_0.LogService"; 332 asyncResp->res.jsonValue["@odata.context"] = 333 "/redfish/v1/$metadata#LogService.LogService"; 334 asyncResp->res.jsonValue["Name"] = "Event Log Service"; 335 asyncResp->res.jsonValue["Description"] = "System Event Log Service"; 336 asyncResp->res.jsonValue["Id"] = "Event Log"; 337 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 338 asyncResp->res.jsonValue["Entries"] = { 339 {"@odata.id", 340 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}}; 341 } 342 }; 343 344 static int fillEventLogEntryJson(const std::string &bmcLogEntryID, 345 const std::string_view &messageID, 346 sd_journal *journal, 347 nlohmann::json &bmcLogEntryJson) 348 { 349 // Get the Log Entry contents 350 int ret = 0; 351 352 std::string_view msg; 353 ret = getJournalMetadata(journal, "MESSAGE", msg); 354 if (ret < 0) 355 { 356 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 357 return 1; 358 } 359 360 // Get the severity from the PRIORITY field 361 int severity = 8; // Default to an invalid priority 362 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 363 if (ret < 0) 364 { 365 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 366 return 1; 367 } 368 369 // Get the MessageArgs from the journal entry by finding all of the 370 // REDFISH_MESSAGE_ARG_x fields 371 const void *data; 372 size_t length; 373 std::vector<std::string> messageArgs; 374 SD_JOURNAL_FOREACH_DATA(journal, data, length) 375 { 376 std::string_view field(static_cast<const char *>(data), length); 377 if (boost::starts_with(field, "REDFISH_MESSAGE_ARG_")) 378 { 379 // Get the Arg number from the field name 380 field.remove_prefix(sizeof("REDFISH_MESSAGE_ARG_") - 1); 381 if (field.empty()) 382 { 383 continue; 384 } 385 unsigned long argNum = std::strtoul(field.data(), nullptr, 10); 386 if (argNum == 0 || argNum > std::numeric_limits<size_t>::max()) 387 { 388 continue; 389 } 390 // Get the Arg value after the "=" character. 391 field.remove_prefix(std::min(field.find("=") + 1, field.size())); 392 // Make sure we have enough space in messageArgs 393 if (argNum > messageArgs.size()) 394 { 395 messageArgs.resize(static_cast<size_t>(argNum)); 396 } 397 messageArgs[argNum - 1] = std::string(field); 398 } 399 } 400 401 // Get the Created time from the timestamp 402 std::string entryTimeStr; 403 if (!getEntryTimestamp(journal, entryTimeStr)) 404 { 405 return 1; 406 } 407 408 // Fill in the log entry with the gathered data 409 bmcLogEntryJson = { 410 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 411 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 412 {"@odata.id", 413 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + 414 bmcLogEntryID}, 415 {"Name", "System Event Log Entry"}, 416 {"Id", bmcLogEntryID}, 417 {"Message", msg}, 418 {"MessageId", messageID}, 419 {"MessageArgs", std::move(messageArgs)}, 420 {"EntryType", "Event"}, 421 {"Severity", 422 severity <= 2 ? "Critical" 423 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""}, 424 {"Created", std::move(entryTimeStr)}}; 425 return 0; 426 } 427 428 class EventLogEntryCollection : public Node 429 { 430 public: 431 template <typename CrowApp> 432 EventLogEntryCollection(CrowApp &app) : 433 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 434 { 435 entityPrivileges = { 436 {boost::beast::http::verb::get, {{"Login"}}}, 437 {boost::beast::http::verb::head, {{"Login"}}}, 438 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 439 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 440 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 441 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 442 } 443 444 private: 445 void doGet(crow::Response &res, const crow::Request &req, 446 const std::vector<std::string> ¶ms) override 447 { 448 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 449 long skip = 0; 450 long top = maxEntriesPerPage; // Show max entries by default 451 if (!getSkipParam(asyncResp->res, req, skip)) 452 { 453 return; 454 } 455 if (!getTopParam(asyncResp->res, req, top)) 456 { 457 return; 458 } 459 // Collections don't include the static data added by SubRoute because 460 // it has a duplicate entry for members 461 asyncResp->res.jsonValue["@odata.type"] = 462 "#LogEntryCollection.LogEntryCollection"; 463 asyncResp->res.jsonValue["@odata.context"] = 464 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 465 asyncResp->res.jsonValue["@odata.id"] = 466 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 467 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 468 asyncResp->res.jsonValue["Description"] = 469 "Collection of System Event Log Entries"; 470 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 471 logEntryArray = nlohmann::json::array(); 472 473 // Go through the journal and create a unique ID for each entry 474 sd_journal *journalTmp = nullptr; 475 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 476 if (ret < 0) 477 { 478 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 479 messages::internalError(asyncResp->res); 480 return; 481 } 482 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 483 journalTmp, sd_journal_close); 484 journalTmp = nullptr; 485 long entryCount = 0; 486 SD_JOURNAL_FOREACH(journal.get()) 487 { 488 // Look for only journal entries that contain a REDFISH_MESSAGE_ID 489 // field 490 std::string_view messageID; 491 ret = getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", 492 messageID); 493 if (ret < 0) 494 { 495 continue; 496 } 497 498 entryCount++; 499 // Handle paging using skip (number of entries to skip from the 500 // start) and top (number of entries to display) 501 if (entryCount <= skip || entryCount > skip + top) 502 { 503 continue; 504 } 505 506 std::string idStr; 507 if (!getUniqueEntryID(journal.get(), idStr)) 508 { 509 continue; 510 } 511 512 logEntryArray.push_back({}); 513 nlohmann::json &bmcLogEntry = logEntryArray.back(); 514 if (fillEventLogEntryJson(idStr, messageID, journal.get(), 515 bmcLogEntry) != 0) 516 { 517 messages::internalError(asyncResp->res); 518 return; 519 } 520 } 521 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 522 if (skip + top < entryCount) 523 { 524 asyncResp->res.jsonValue["Members@odata.nextLink"] = 525 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries?$skip=" + 526 std::to_string(skip + top); 527 } 528 } 529 }; 530 531 class EventLogEntry : public Node 532 { 533 public: 534 EventLogEntry(CrowApp &app) : 535 Node(app, 536 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/", 537 std::string()) 538 { 539 entityPrivileges = { 540 {boost::beast::http::verb::get, {{"Login"}}}, 541 {boost::beast::http::verb::head, {{"Login"}}}, 542 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 543 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 544 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 545 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 546 } 547 548 private: 549 void doGet(crow::Response &res, const crow::Request &req, 550 const std::vector<std::string> ¶ms) override 551 { 552 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 553 if (params.size() != 1) 554 { 555 messages::internalError(asyncResp->res); 556 return; 557 } 558 const std::string &entryID = params[0]; 559 // Convert the unique ID back to a timestamp to find the entry 560 uint64_t ts = 0; 561 uint16_t index = 0; 562 if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 563 { 564 return; 565 } 566 567 sd_journal *journalTmp = nullptr; 568 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 569 if (ret < 0) 570 { 571 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 572 messages::internalError(asyncResp->res); 573 return; 574 } 575 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 576 journalTmp, sd_journal_close); 577 journalTmp = nullptr; 578 // Go to the timestamp in the log and move to the entry at the index 579 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 580 for (int i = 0; i <= index; i++) 581 { 582 sd_journal_next(journal.get()); 583 } 584 // Confirm that the entry ID matches what was requested 585 std::string idStr; 586 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID) 587 { 588 messages::resourceMissingAtURI(asyncResp->res, entryID); 589 return; 590 } 591 592 // only use journal entries that contain a REDFISH_MESSAGE_ID field 593 std::string_view messageID; 594 ret = 595 getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", messageID); 596 if (ret < 0) 597 { 598 messages::resourceNotFound(asyncResp->res, "LogEntry", "system"); 599 return; 600 } 601 602 if (fillEventLogEntryJson(entryID, messageID, journal.get(), 603 asyncResp->res.jsonValue) != 0) 604 { 605 messages::internalError(asyncResp->res); 606 return; 607 } 608 } 609 }; 610 611 class BMCLogServiceCollection : public Node 612 { 613 public: 614 template <typename CrowApp> 615 BMCLogServiceCollection(CrowApp &app) : 616 Node(app, "/redfish/v1/Managers/bmc/LogServices/") 617 { 618 entityPrivileges = { 619 {boost::beast::http::verb::get, {{"Login"}}}, 620 {boost::beast::http::verb::head, {{"Login"}}}, 621 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 622 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 623 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 624 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 625 } 626 627 private: 628 /** 629 * Functions triggers appropriate requests on DBus 630 */ 631 void doGet(crow::Response &res, const crow::Request &req, 632 const std::vector<std::string> ¶ms) override 633 { 634 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 635 // Collections don't include the static data added by SubRoute because 636 // it has a duplicate entry for members 637 asyncResp->res.jsonValue["@odata.type"] = 638 "#LogServiceCollection.LogServiceCollection"; 639 asyncResp->res.jsonValue["@odata.context"] = 640 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; 641 asyncResp->res.jsonValue["@odata.id"] = 642 "/redfish/v1/Managers/bmc/LogServices"; 643 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; 644 asyncResp->res.jsonValue["Description"] = 645 "Collection of LogServices for this Manager"; 646 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 647 logServiceArray = nlohmann::json::array(); 648 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 649 logServiceArray.push_back( 650 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}}); 651 #endif 652 asyncResp->res.jsonValue["Members@odata.count"] = 653 logServiceArray.size(); 654 } 655 }; 656 657 class BMCJournalLogService : public Node 658 { 659 public: 660 template <typename CrowApp> 661 BMCJournalLogService(CrowApp &app) : 662 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 663 { 664 entityPrivileges = { 665 {boost::beast::http::verb::get, {{"Login"}}}, 666 {boost::beast::http::verb::head, {{"Login"}}}, 667 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 668 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 669 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 670 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 671 } 672 673 private: 674 void doGet(crow::Response &res, const crow::Request &req, 675 const std::vector<std::string> ¶ms) override 676 { 677 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 678 asyncResp->res.jsonValue["@odata.type"] = 679 "#LogService.v1_1_0.LogService"; 680 asyncResp->res.jsonValue["@odata.id"] = 681 "/redfish/v1/Managers/bmc/LogServices/Journal"; 682 asyncResp->res.jsonValue["@odata.context"] = 683 "/redfish/v1/$metadata#LogService.LogService"; 684 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 685 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 686 asyncResp->res.jsonValue["Id"] = "BMC Journal"; 687 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 688 asyncResp->res.jsonValue["Entries"] = { 689 {"@odata.id", 690 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/"}}; 691 } 692 }; 693 694 static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID, 695 sd_journal *journal, 696 nlohmann::json &bmcJournalLogEntryJson) 697 { 698 // Get the Log Entry contents 699 int ret = 0; 700 701 std::string_view msg; 702 ret = getJournalMetadata(journal, "MESSAGE", msg); 703 if (ret < 0) 704 { 705 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 706 return 1; 707 } 708 709 // Get the severity from the PRIORITY field 710 int severity = 8; // Default to an invalid priority 711 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 712 if (ret < 0) 713 { 714 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 715 return 1; 716 } 717 718 // Get the Created time from the timestamp 719 std::string entryTimeStr; 720 if (!getEntryTimestamp(journal, entryTimeStr)) 721 { 722 return 1; 723 } 724 725 // Fill in the log entry with the gathered data 726 bmcJournalLogEntryJson = { 727 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 728 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 729 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + 730 bmcJournalLogEntryID}, 731 {"Name", "BMC Journal Entry"}, 732 {"Id", bmcJournalLogEntryID}, 733 {"Message", msg}, 734 {"EntryType", "Oem"}, 735 {"Severity", 736 severity <= 2 ? "Critical" 737 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""}, 738 {"OemRecordFormat", "Intel BMC Journal Entry"}, 739 {"Created", std::move(entryTimeStr)}}; 740 return 0; 741 } 742 743 class BMCJournalLogEntryCollection : public Node 744 { 745 public: 746 template <typename CrowApp> 747 BMCJournalLogEntryCollection(CrowApp &app) : 748 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 749 { 750 entityPrivileges = { 751 {boost::beast::http::verb::get, {{"Login"}}}, 752 {boost::beast::http::verb::head, {{"Login"}}}, 753 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 754 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 755 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 756 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 757 } 758 759 private: 760 void doGet(crow::Response &res, const crow::Request &req, 761 const std::vector<std::string> ¶ms) override 762 { 763 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 764 static constexpr const long maxEntriesPerPage = 1000; 765 long skip = 0; 766 long top = maxEntriesPerPage; // Show max entries by default 767 if (!getSkipParam(asyncResp->res, req, skip)) 768 { 769 return; 770 } 771 if (!getTopParam(asyncResp->res, req, top)) 772 { 773 return; 774 } 775 // Collections don't include the static data added by SubRoute because 776 // it has a duplicate entry for members 777 asyncResp->res.jsonValue["@odata.type"] = 778 "#LogEntryCollection.LogEntryCollection"; 779 asyncResp->res.jsonValue["@odata.id"] = 780 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 781 asyncResp->res.jsonValue["@odata.context"] = 782 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 783 asyncResp->res.jsonValue["@odata.id"] = 784 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 785 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 786 asyncResp->res.jsonValue["Description"] = 787 "Collection of BMC Journal Entries"; 788 asyncResp->res.jsonValue["@odata.id"] = 789 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries"; 790 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 791 logEntryArray = nlohmann::json::array(); 792 793 // Go through the journal and use the timestamp to create a unique ID 794 // for each entry 795 sd_journal *journalTmp = nullptr; 796 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 797 if (ret < 0) 798 { 799 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 800 messages::internalError(asyncResp->res); 801 return; 802 } 803 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 804 journalTmp, sd_journal_close); 805 journalTmp = nullptr; 806 long entryCount = 0; 807 SD_JOURNAL_FOREACH(journal.get()) 808 { 809 entryCount++; 810 // Handle paging using skip (number of entries to skip from the 811 // start) and top (number of entries to display) 812 if (entryCount <= skip || entryCount > skip + top) 813 { 814 continue; 815 } 816 817 std::string idStr; 818 if (!getUniqueEntryID(journal.get(), idStr)) 819 { 820 continue; 821 } 822 823 logEntryArray.push_back({}); 824 nlohmann::json &bmcJournalLogEntry = logEntryArray.back(); 825 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 826 bmcJournalLogEntry) != 0) 827 { 828 messages::internalError(asyncResp->res); 829 return; 830 } 831 } 832 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 833 if (skip + top < entryCount) 834 { 835 asyncResp->res.jsonValue["Members@odata.nextLink"] = 836 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 837 std::to_string(skip + top); 838 } 839 } 840 }; 841 842 class BMCJournalLogEntry : public Node 843 { 844 public: 845 BMCJournalLogEntry(CrowApp &app) : 846 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/", 847 std::string()) 848 { 849 entityPrivileges = { 850 {boost::beast::http::verb::get, {{"Login"}}}, 851 {boost::beast::http::verb::head, {{"Login"}}}, 852 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 853 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 854 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 855 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 856 } 857 858 private: 859 void doGet(crow::Response &res, const crow::Request &req, 860 const std::vector<std::string> ¶ms) override 861 { 862 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 863 if (params.size() != 1) 864 { 865 messages::internalError(asyncResp->res); 866 return; 867 } 868 const std::string &entryID = params[0]; 869 // Convert the unique ID back to a timestamp to find the entry 870 uint64_t ts = 0; 871 uint16_t index = 0; 872 if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 873 { 874 return; 875 } 876 877 sd_journal *journalTmp = nullptr; 878 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 879 if (ret < 0) 880 { 881 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 882 messages::internalError(asyncResp->res); 883 return; 884 } 885 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 886 journalTmp, sd_journal_close); 887 journalTmp = nullptr; 888 // Go to the timestamp in the log and move to the entry at the index 889 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 890 for (int i = 0; i <= index; i++) 891 { 892 sd_journal_next(journal.get()); 893 } 894 // Confirm that the entry ID matches what was requested 895 std::string idStr; 896 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID) 897 { 898 messages::resourceMissingAtURI(asyncResp->res, entryID); 899 return; 900 } 901 902 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 903 asyncResp->res.jsonValue) != 0) 904 { 905 messages::internalError(asyncResp->res); 906 return; 907 } 908 } 909 }; 910 911 class CPULogService : public Node 912 { 913 public: 914 template <typename CrowApp> 915 CPULogService(CrowApp &app) : 916 Node(app, "/redfish/v1/Systems/system/LogServices/CpuLog/") 917 { 918 entityPrivileges = { 919 {boost::beast::http::verb::get, {{"Login"}}}, 920 {boost::beast::http::verb::head, {{"Login"}}}, 921 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 922 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 923 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 924 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 925 } 926 927 private: 928 /** 929 * Functions triggers appropriate requests on DBus 930 */ 931 void doGet(crow::Response &res, const crow::Request &req, 932 const std::vector<std::string> ¶ms) override 933 { 934 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 935 // Copy over the static data to include the entries added by SubRoute 936 asyncResp->res.jsonValue["@odata.id"] = 937 "/redfish/v1/Systems/system/LogServices/CpuLog"; 938 asyncResp->res.jsonValue["@odata.type"] = 939 "#LogService.v1_1_0.LogService"; 940 asyncResp->res.jsonValue["@odata.context"] = 941 "/redfish/v1/$metadata#LogService.LogService"; 942 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service"; 943 asyncResp->res.jsonValue["Description"] = "CPU Log Service"; 944 asyncResp->res.jsonValue["Id"] = "CPU Log"; 945 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 946 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 947 asyncResp->res.jsonValue["Entries"] = { 948 {"@odata.id", 949 "/redfish/v1/Systems/system/LogServices/CpuLog/Entries"}}; 950 asyncResp->res.jsonValue["Actions"] = { 951 {"Oem", 952 {{"#CpuLog.Immediate", 953 {{"target", "/redfish/v1/Systems/system/LogServices/CpuLog/" 954 "Actions/Oem/CpuLog.Immediate"}}}}}}; 955 956 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI 957 asyncResp->res.jsonValue["Actions"]["Oem"].push_back( 958 {"#CpuLog.SendRawPeci", 959 {{"target", "/redfish/v1/Systems/system/LogServices/CpuLog/" 960 "Actions/Oem/CpuLog.SendRawPeci"}}}); 961 #endif 962 } 963 }; 964 965 class CPULogEntryCollection : public Node 966 { 967 public: 968 template <typename CrowApp> 969 CPULogEntryCollection(CrowApp &app) : 970 Node(app, "/redfish/v1/Systems/system/LogServices/CpuLog/Entries/") 971 { 972 entityPrivileges = { 973 {boost::beast::http::verb::get, {{"Login"}}}, 974 {boost::beast::http::verb::head, {{"Login"}}}, 975 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 976 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 977 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 978 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 979 } 980 981 private: 982 /** 983 * Functions triggers appropriate requests on DBus 984 */ 985 void doGet(crow::Response &res, const crow::Request &req, 986 const std::vector<std::string> ¶ms) override 987 { 988 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 989 // Collections don't include the static data added by SubRoute because 990 // it has a duplicate entry for members 991 auto getLogEntriesCallback = [asyncResp]( 992 const boost::system::error_code ec, 993 const std::vector<std::string> &resp) { 994 if (ec) 995 { 996 if (ec.value() != 997 boost::system::errc::no_such_file_or_directory) 998 { 999 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 1000 << ec.message(); 1001 messages::internalError(asyncResp->res); 1002 return; 1003 } 1004 } 1005 asyncResp->res.jsonValue["@odata.type"] = 1006 "#LogEntryCollection.LogEntryCollection"; 1007 asyncResp->res.jsonValue["@odata.id"] = 1008 "/redfish/v1/Systems/system/LogServices/CpuLog/Entries"; 1009 asyncResp->res.jsonValue["@odata.context"] = 1010 "/redfish/v1/" 1011 "$metadata#LogEntryCollection.LogEntryCollection"; 1012 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries"; 1013 asyncResp->res.jsonValue["Description"] = 1014 "Collection of CPU Log Entries"; 1015 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1016 logEntryArray = nlohmann::json::array(); 1017 for (const std::string &objpath : resp) 1018 { 1019 // Don't list the immediate log 1020 if (objpath.compare(cpuLogImmediatePath) == 0) 1021 { 1022 continue; 1023 } 1024 std::size_t lastPos = objpath.rfind("/"); 1025 if (lastPos != std::string::npos) 1026 { 1027 logEntryArray.push_back( 1028 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/" 1029 "CpuLog/Entries/" + 1030 objpath.substr(lastPos + 1)}}); 1031 } 1032 } 1033 asyncResp->res.jsonValue["Members@odata.count"] = 1034 logEntryArray.size(); 1035 }; 1036 crow::connections::systemBus->async_method_call( 1037 std::move(getLogEntriesCallback), 1038 "xyz.openbmc_project.ObjectMapper", 1039 "/xyz/openbmc_project/object_mapper", 1040 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 1041 std::array<const char *, 1>{cpuLogInterface}); 1042 } 1043 }; 1044 1045 std::string getLogCreatedTime(const nlohmann::json &cpuLog) 1046 { 1047 nlohmann::json::const_iterator cdIt = cpuLog.find("crashlog_data"); 1048 if (cdIt != cpuLog.end()) 1049 { 1050 nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO"); 1051 if (siIt != cdIt->end()) 1052 { 1053 nlohmann::json::const_iterator tsIt = siIt->find("timestamp"); 1054 if (tsIt != siIt->end()) 1055 { 1056 const std::string *logTime = 1057 tsIt->get_ptr<const std::string *>(); 1058 if (logTime != nullptr) 1059 { 1060 return *logTime; 1061 } 1062 } 1063 } 1064 } 1065 BMCWEB_LOG_DEBUG << "failed to find log timestamp"; 1066 1067 return std::string(); 1068 } 1069 1070 class CPULogEntry : public Node 1071 { 1072 public: 1073 CPULogEntry(CrowApp &app) : 1074 Node(app, 1075 "/redfish/v1/Systems/system/LogServices/CpuLog/Entries/<str>/", 1076 std::string()) 1077 { 1078 entityPrivileges = { 1079 {boost::beast::http::verb::get, {{"Login"}}}, 1080 {boost::beast::http::verb::head, {{"Login"}}}, 1081 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1082 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1083 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1084 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1085 } 1086 1087 private: 1088 void doGet(crow::Response &res, const crow::Request &req, 1089 const std::vector<std::string> ¶ms) override 1090 { 1091 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1092 if (params.size() != 1) 1093 { 1094 messages::internalError(asyncResp->res); 1095 return; 1096 } 1097 const int logId = std::atoi(params[0].c_str()); 1098 auto getStoredLogCallback = [asyncResp, logId]( 1099 const boost::system::error_code ec, 1100 const std::variant<std::string> &resp) { 1101 if (ec) 1102 { 1103 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 1104 messages::internalError(asyncResp->res); 1105 return; 1106 } 1107 const std::string *log = std::get_if<std::string>(&resp); 1108 if (log == nullptr) 1109 { 1110 messages::internalError(asyncResp->res); 1111 return; 1112 } 1113 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 1114 if (j.is_discarded()) 1115 { 1116 messages::internalError(asyncResp->res); 1117 return; 1118 } 1119 std::string t = getLogCreatedTime(j); 1120 asyncResp->res.jsonValue = { 1121 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 1122 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1123 {"@odata.id", 1124 "/redfish/v1/Systems/system/LogServices/CpuLog/Entries/" + 1125 std::to_string(logId)}, 1126 {"Name", "CPU Debug Log"}, 1127 {"Id", logId}, 1128 {"EntryType", "Oem"}, 1129 {"OemRecordFormat", "Intel CPU Log"}, 1130 {"Oem", {{"Intel", std::move(j)}}}, 1131 {"Created", std::move(t)}}; 1132 }; 1133 crow::connections::systemBus->async_method_call( 1134 std::move(getStoredLogCallback), cpuLogObject, 1135 cpuLogPath + std::string("/") + std::to_string(logId), 1136 "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log"); 1137 } 1138 }; 1139 1140 class ImmediateCPULog : public Node 1141 { 1142 public: 1143 ImmediateCPULog(CrowApp &app) : 1144 Node(app, "/redfish/v1/Systems/system/LogServices/CpuLog/Actions/Oem/" 1145 "CpuLog.Immediate/") 1146 { 1147 entityPrivileges = { 1148 {boost::beast::http::verb::get, {{"Login"}}}, 1149 {boost::beast::http::verb::head, {{"Login"}}}, 1150 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1151 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1152 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1153 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1154 } 1155 1156 private: 1157 void doPost(crow::Response &res, const crow::Request &req, 1158 const std::vector<std::string> ¶ms) override 1159 { 1160 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1161 static std::unique_ptr<sdbusplus::bus::match::match> 1162 immediateLogMatcher; 1163 1164 // Only allow one Immediate Log request at a time 1165 if (immediateLogMatcher != nullptr) 1166 { 1167 asyncResp->res.addHeader("Retry-After", "30"); 1168 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 1169 return; 1170 } 1171 // Make this static so it survives outside this method 1172 static boost::asio::deadline_timer timeout(*req.ioService); 1173 1174 timeout.expires_from_now(boost::posix_time::seconds(30)); 1175 timeout.async_wait([asyncResp](const boost::system::error_code &ec) { 1176 immediateLogMatcher = nullptr; 1177 if (ec) 1178 { 1179 // operation_aborted is expected if timer is canceled before 1180 // completion. 1181 if (ec != boost::asio::error::operation_aborted) 1182 { 1183 BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 1184 } 1185 return; 1186 } 1187 BMCWEB_LOG_ERROR << "Timed out waiting for immediate log"; 1188 1189 messages::internalError(asyncResp->res); 1190 }); 1191 1192 auto immediateLogMatcherCallback = [asyncResp]( 1193 sdbusplus::message::message &m) { 1194 BMCWEB_LOG_DEBUG << "Immediate log available match fired"; 1195 boost::system::error_code ec; 1196 timeout.cancel(ec); 1197 if (ec) 1198 { 1199 BMCWEB_LOG_ERROR << "error canceling timer " << ec; 1200 } 1201 sdbusplus::message::object_path objPath; 1202 boost::container::flat_map< 1203 std::string, boost::container::flat_map< 1204 std::string, std::variant<std::string>>> 1205 interfacesAdded; 1206 m.read(objPath, interfacesAdded); 1207 const std::string *log = std::get_if<std::string>( 1208 &interfacesAdded[cpuLogInterface]["Log"]); 1209 if (log == nullptr) 1210 { 1211 messages::internalError(asyncResp->res); 1212 // Careful with immediateLogMatcher. It is a unique_ptr to the 1213 // match object inside which this lambda is executing. Once it 1214 // is set to nullptr, the match object will be destroyed and the 1215 // lambda will lose its context, including res, so it needs to 1216 // be the last thing done. 1217 immediateLogMatcher = nullptr; 1218 return; 1219 } 1220 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 1221 if (j.is_discarded()) 1222 { 1223 messages::internalError(asyncResp->res); 1224 // Careful with immediateLogMatcher. It is a unique_ptr to the 1225 // match object inside which this lambda is executing. Once it 1226 // is set to nullptr, the match object will be destroyed and the 1227 // lambda will lose its context, including res, so it needs to 1228 // be the last thing done. 1229 immediateLogMatcher = nullptr; 1230 return; 1231 } 1232 std::string t = getLogCreatedTime(j); 1233 asyncResp->res.jsonValue = { 1234 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 1235 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1236 {"Name", "CPU Debug Log"}, 1237 {"EntryType", "Oem"}, 1238 {"OemRecordFormat", "Intel CPU Log"}, 1239 {"Oem", {{"Intel", std::move(j)}}}, 1240 {"Created", std::move(t)}}; 1241 // Careful with immediateLogMatcher. It is a unique_ptr to the 1242 // match object inside which this lambda is executing. Once it is 1243 // set to nullptr, the match object will be destroyed and the lambda 1244 // will lose its context, including res, so it needs to be the last 1245 // thing done. 1246 immediateLogMatcher = nullptr; 1247 }; 1248 immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>( 1249 *crow::connections::systemBus, 1250 sdbusplus::bus::match::rules::interfacesAdded() + 1251 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath), 1252 std::move(immediateLogMatcherCallback)); 1253 1254 auto generateImmediateLogCallback = 1255 [asyncResp](const boost::system::error_code ec, 1256 const std::string &resp) { 1257 if (ec) 1258 { 1259 if (ec.value() == 1260 boost::system::errc::operation_not_supported) 1261 { 1262 messages::resourceInStandby(asyncResp->res); 1263 } 1264 else 1265 { 1266 messages::internalError(asyncResp->res); 1267 } 1268 boost::system::error_code timeoutec; 1269 timeout.cancel(timeoutec); 1270 if (timeoutec) 1271 { 1272 BMCWEB_LOG_ERROR << "error canceling timer " 1273 << timeoutec; 1274 } 1275 immediateLogMatcher = nullptr; 1276 return; 1277 } 1278 }; 1279 crow::connections::systemBus->async_method_call( 1280 std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath, 1281 cpuLogImmediateInterface, "GenerateImmediateLog"); 1282 } 1283 }; 1284 1285 class SendRawPECI : public Node 1286 { 1287 public: 1288 SendRawPECI(CrowApp &app) : 1289 Node(app, "/redfish/v1/Systems/system/LogServices/CpuLog/Actions/Oem/" 1290 "CpuLog.SendRawPeci/") 1291 { 1292 entityPrivileges = { 1293 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 1294 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 1295 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1296 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1297 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1298 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1299 } 1300 1301 private: 1302 void doPost(crow::Response &res, const crow::Request &req, 1303 const std::vector<std::string> ¶ms) override 1304 { 1305 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1306 uint8_t clientAddress = 0; 1307 uint8_t readLength = 0; 1308 std::vector<uint8_t> peciCommand; 1309 if (!json_util::readJson(req, res, "ClientAddress", clientAddress, 1310 "ReadLength", readLength, "PECICommand", 1311 peciCommand)) 1312 { 1313 return; 1314 } 1315 1316 // Callback to return the Raw PECI response 1317 auto sendRawPECICallback = 1318 [asyncResp](const boost::system::error_code ec, 1319 const std::vector<uint8_t> &resp) { 1320 if (ec) 1321 { 1322 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: " 1323 << ec.message(); 1324 messages::internalError(asyncResp->res); 1325 return; 1326 } 1327 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"}, 1328 {"PECIResponse", resp}}; 1329 }; 1330 // Call the SendRawPECI command with the provided data 1331 crow::connections::systemBus->async_method_call( 1332 std::move(sendRawPECICallback), cpuLogObject, cpuLogPath, 1333 cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength, 1334 peciCommand); 1335 } 1336 }; 1337 1338 } // namespace redfish 1339