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 field 590 boost::string_view messageID; 591 ret = 592 getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", messageID); 593 if (ret < 0) 594 { 595 messages::resourceNotFound(asyncResp->res, "LogEntry", "system"); 596 return; 597 } 598 599 if (fillEventLogEntryJson(entryID, messageID, journal.get(), 600 asyncResp->res.jsonValue) != 0) 601 { 602 messages::internalError(asyncResp->res); 603 return; 604 } 605 } 606 }; 607 608 class BMCLogServiceCollection : public Node 609 { 610 public: 611 template <typename CrowApp> 612 BMCLogServiceCollection(CrowApp &app) : 613 Node(app, "/redfish/v1/Managers/bmc/LogServices/") 614 { 615 entityPrivileges = { 616 {boost::beast::http::verb::get, {{"Login"}}}, 617 {boost::beast::http::verb::head, {{"Login"}}}, 618 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 619 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 620 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 621 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 622 } 623 624 private: 625 /** 626 * Functions triggers appropriate requests on DBus 627 */ 628 void doGet(crow::Response &res, const crow::Request &req, 629 const std::vector<std::string> ¶ms) override 630 { 631 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 632 // Collections don't include the static data added by SubRoute because 633 // it has a duplicate entry for members 634 asyncResp->res.jsonValue["@odata.type"] = 635 "#LogServiceCollection.LogServiceCollection"; 636 asyncResp->res.jsonValue["@odata.context"] = 637 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; 638 asyncResp->res.jsonValue["@odata.id"] = 639 "/redfish/v1/Managers/bmc/LogServices"; 640 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; 641 asyncResp->res.jsonValue["Description"] = 642 "Collection of LogServices for this Manager"; 643 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 644 logServiceArray = nlohmann::json::array(); 645 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 646 logServiceArray.push_back( 647 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}}); 648 #endif 649 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG 650 logServiceArray.push_back( 651 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/CpuLog"}}); 652 #endif 653 asyncResp->res.jsonValue["Members@odata.count"] = 654 logServiceArray.size(); 655 } 656 }; 657 658 class BMCJournalLogService : public Node 659 { 660 public: 661 template <typename CrowApp> 662 BMCJournalLogService(CrowApp &app) : 663 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 664 { 665 entityPrivileges = { 666 {boost::beast::http::verb::get, {{"Login"}}}, 667 {boost::beast::http::verb::head, {{"Login"}}}, 668 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 669 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 670 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 671 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 672 } 673 674 private: 675 void doGet(crow::Response &res, const crow::Request &req, 676 const std::vector<std::string> ¶ms) override 677 { 678 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 679 asyncResp->res.jsonValue["@odata.type"] = 680 "#LogService.v1_1_0.LogService"; 681 asyncResp->res.jsonValue["@odata.id"] = 682 "/redfish/v1/Managers/bmc/LogServices/Journal"; 683 asyncResp->res.jsonValue["@odata.context"] = 684 "/redfish/v1/$metadata#LogService.LogService"; 685 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 686 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 687 asyncResp->res.jsonValue["Id"] = "BMC Journal"; 688 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 689 asyncResp->res.jsonValue["Entries"] = { 690 {"@odata.id", 691 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/"}}; 692 } 693 }; 694 695 static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID, 696 sd_journal *journal, 697 nlohmann::json &bmcJournalLogEntryJson) 698 { 699 // Get the Log Entry contents 700 int ret = 0; 701 702 boost::string_view msg; 703 ret = getJournalMetadata(journal, "MESSAGE", msg); 704 if (ret < 0) 705 { 706 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 707 return 1; 708 } 709 710 // Get the severity from the PRIORITY field 711 int severity = 8; // Default to an invalid priority 712 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 713 if (ret < 0) 714 { 715 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 716 return 1; 717 } 718 719 // Get the Created time from the timestamp 720 std::string entryTimeStr; 721 if (!getEntryTimestamp(journal, entryTimeStr)) 722 { 723 return 1; 724 } 725 726 // Fill in the log entry with the gathered data 727 bmcJournalLogEntryJson = { 728 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 729 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 730 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + 731 bmcJournalLogEntryID}, 732 {"Name", "BMC Journal Entry"}, 733 {"Id", bmcJournalLogEntryID}, 734 {"Message", msg}, 735 {"EntryType", "Oem"}, 736 {"Severity", 737 severity <= 2 ? "Critical" 738 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""}, 739 {"OemRecordFormat", "Intel BMC Journal Entry"}, 740 {"Created", std::move(entryTimeStr)}}; 741 return 0; 742 } 743 744 class BMCJournalLogEntryCollection : public Node 745 { 746 public: 747 template <typename CrowApp> 748 BMCJournalLogEntryCollection(CrowApp &app) : 749 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 750 { 751 entityPrivileges = { 752 {boost::beast::http::verb::get, {{"Login"}}}, 753 {boost::beast::http::verb::head, {{"Login"}}}, 754 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 755 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 756 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 757 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 758 } 759 760 private: 761 void doGet(crow::Response &res, const crow::Request &req, 762 const std::vector<std::string> ¶ms) override 763 { 764 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 765 static constexpr const long maxEntriesPerPage = 1000; 766 long skip = 0; 767 long top = maxEntriesPerPage; // Show max entries by default 768 if (!getSkipParam(asyncResp->res, req, skip)) 769 { 770 return; 771 } 772 if (!getTopParam(asyncResp->res, req, top)) 773 { 774 return; 775 } 776 // Collections don't include the static data added by SubRoute because 777 // it has a duplicate entry for members 778 asyncResp->res.jsonValue["@odata.type"] = 779 "#LogEntryCollection.LogEntryCollection"; 780 asyncResp->res.jsonValue["@odata.id"] = 781 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 782 asyncResp->res.jsonValue["@odata.context"] = 783 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 784 asyncResp->res.jsonValue["@odata.id"] = 785 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 786 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 787 asyncResp->res.jsonValue["Description"] = 788 "Collection of BMC Journal Entries"; 789 asyncResp->res.jsonValue["@odata.id"] = 790 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries"; 791 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 792 logEntryArray = nlohmann::json::array(); 793 794 // Go through the journal and use the timestamp to create a unique ID 795 // for each entry 796 sd_journal *journalTmp = nullptr; 797 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 798 if (ret < 0) 799 { 800 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 801 messages::internalError(asyncResp->res); 802 return; 803 } 804 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 805 journalTmp, sd_journal_close); 806 journalTmp = nullptr; 807 uint64_t entryCount = 0; 808 SD_JOURNAL_FOREACH(journal.get()) 809 { 810 entryCount++; 811 // Handle paging using skip (number of entries to skip from the 812 // start) and top (number of entries to display) 813 if (entryCount <= skip || entryCount > skip + top) 814 { 815 continue; 816 } 817 818 std::string idStr; 819 if (!getUniqueEntryID(journal.get(), idStr)) 820 { 821 continue; 822 } 823 824 logEntryArray.push_back({}); 825 nlohmann::json &bmcJournalLogEntry = logEntryArray.back(); 826 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 827 bmcJournalLogEntry) != 0) 828 { 829 messages::internalError(asyncResp->res); 830 return; 831 } 832 } 833 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 834 if (skip + top < entryCount) 835 { 836 asyncResp->res.jsonValue["Members@odata.nextLink"] = 837 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 838 std::to_string(skip + top); 839 } 840 } 841 }; 842 843 class BMCJournalLogEntry : public Node 844 { 845 public: 846 BMCJournalLogEntry(CrowApp &app) : 847 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/", 848 std::string()) 849 { 850 entityPrivileges = { 851 {boost::beast::http::verb::get, {{"Login"}}}, 852 {boost::beast::http::verb::head, {{"Login"}}}, 853 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 854 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 855 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 856 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 857 } 858 859 private: 860 void doGet(crow::Response &res, const crow::Request &req, 861 const std::vector<std::string> ¶ms) override 862 { 863 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 864 if (params.size() != 1) 865 { 866 messages::internalError(asyncResp->res); 867 return; 868 } 869 const std::string &entryID = params[0]; 870 // Convert the unique ID back to a timestamp to find the entry 871 uint64_t ts = 0; 872 uint16_t index = 0; 873 if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 874 { 875 return; 876 } 877 878 sd_journal *journalTmp = nullptr; 879 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 880 if (ret < 0) 881 { 882 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 883 messages::internalError(asyncResp->res); 884 return; 885 } 886 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 887 journalTmp, sd_journal_close); 888 journalTmp = nullptr; 889 // Go to the timestamp in the log and move to the entry at the index 890 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 891 for (int i = 0; i <= index; i++) 892 { 893 sd_journal_next(journal.get()); 894 } 895 // Confirm that the entry ID matches what was requested 896 std::string idStr; 897 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID) 898 { 899 messages::resourceMissingAtURI(asyncResp->res, entryID); 900 return; 901 } 902 903 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 904 asyncResp->res.jsonValue) != 0) 905 { 906 messages::internalError(asyncResp->res); 907 return; 908 } 909 } 910 }; 911 912 class CPULogService : public Node 913 { 914 public: 915 template <typename CrowApp> 916 CPULogService(CrowApp &app) : 917 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/") 918 { 919 entityPrivileges = { 920 {boost::beast::http::verb::get, {{"Login"}}}, 921 {boost::beast::http::verb::head, {{"Login"}}}, 922 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 923 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 924 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 925 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 926 } 927 928 private: 929 /** 930 * Functions triggers appropriate requests on DBus 931 */ 932 void doGet(crow::Response &res, const crow::Request &req, 933 const std::vector<std::string> ¶ms) override 934 { 935 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 936 // Copy over the static data to include the entries added by SubRoute 937 asyncResp->res.jsonValue["@odata.id"] = 938 "/redfish/v1/Managers/bmc/LogServices/CpuLog"; 939 asyncResp->res.jsonValue["@odata.type"] = 940 "#LogService.v1_1_0.LogService"; 941 asyncResp->res.jsonValue["@odata.context"] = 942 "/redfish/v1/$metadata#LogService.LogService"; 943 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service"; 944 asyncResp->res.jsonValue["Description"] = "CPU Log Service"; 945 asyncResp->res.jsonValue["Id"] = "CPU Log"; 946 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 947 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 948 asyncResp->res.jsonValue["Entries"] = { 949 {"@odata.id", 950 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries"}}; 951 asyncResp->res.jsonValue["Actions"] = { 952 {"Oem", 953 {{"#CpuLog.Immediate", 954 {{"target", "/redfish/v1/Managers/bmc/LogServices/CpuLog/" 955 "Actions/Oem/CpuLog.Immediate"}}}}}}; 956 957 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI 958 asyncResp->res.jsonValue["Actions"]["Oem"].push_back( 959 {"#CpuLog.SendRawPeci", 960 {{"target", "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/" 961 "Oem/CpuLog.SendRawPeci"}}}); 962 #endif 963 } 964 }; 965 966 class CPULogEntryCollection : public Node 967 { 968 public: 969 template <typename CrowApp> 970 CPULogEntryCollection(CrowApp &app) : 971 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/") 972 { 973 entityPrivileges = { 974 {boost::beast::http::verb::get, {{"Login"}}}, 975 {boost::beast::http::verb::head, {{"Login"}}}, 976 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 977 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 978 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 979 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 980 } 981 982 private: 983 /** 984 * Functions triggers appropriate requests on DBus 985 */ 986 void doGet(crow::Response &res, const crow::Request &req, 987 const std::vector<std::string> ¶ms) override 988 { 989 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 990 // Collections don't include the static data added by SubRoute because 991 // it has a duplicate entry for members 992 auto getLogEntriesCallback = [asyncResp]( 993 const boost::system::error_code ec, 994 const std::vector<std::string> &resp) { 995 if (ec) 996 { 997 if (ec.value() != 998 boost::system::errc::no_such_file_or_directory) 999 { 1000 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 1001 << ec.message(); 1002 messages::internalError(asyncResp->res); 1003 return; 1004 } 1005 } 1006 asyncResp->res.jsonValue["@odata.type"] = 1007 "#LogEntryCollection.LogEntryCollection"; 1008 asyncResp->res.jsonValue["@odata.id"] = 1009 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries"; 1010 asyncResp->res.jsonValue["@odata.context"] = 1011 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 1012 asyncResp->res.jsonValue["@odata.id"] = 1013 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries"; 1014 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries"; 1015 asyncResp->res.jsonValue["Description"] = 1016 "Collection of CPU Log Entries"; 1017 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1018 logEntryArray = nlohmann::json::array(); 1019 for (const std::string &objpath : resp) 1020 { 1021 // Don't list the immediate log 1022 if (objpath.compare(cpuLogImmediatePath) == 0) 1023 { 1024 continue; 1025 } 1026 std::size_t lastPos = objpath.rfind("/"); 1027 if (lastPos != std::string::npos) 1028 { 1029 logEntryArray.push_back( 1030 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/" 1031 "CpuLog/Entries/" + 1032 objpath.substr(lastPos + 1)}}); 1033 } 1034 } 1035 asyncResp->res.jsonValue["Members@odata.count"] = 1036 logEntryArray.size(); 1037 }; 1038 crow::connections::systemBus->async_method_call( 1039 std::move(getLogEntriesCallback), 1040 "xyz.openbmc_project.ObjectMapper", 1041 "/xyz/openbmc_project/object_mapper", 1042 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 1043 std::array<const char *, 1>{cpuLogInterface}); 1044 } 1045 }; 1046 1047 std::string getLogCreatedTime(const nlohmann::json &cpuLog) 1048 { 1049 nlohmann::json::const_iterator cdIt = cpuLog.find("crashlog_data"); 1050 if (cdIt != cpuLog.end()) 1051 { 1052 nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO"); 1053 if (siIt != cdIt->end()) 1054 { 1055 nlohmann::json::const_iterator tsIt = siIt->find("timestamp"); 1056 if (tsIt != siIt->end()) 1057 { 1058 const std::string *logTime = 1059 tsIt->get_ptr<const std::string *>(); 1060 if (logTime != nullptr) 1061 { 1062 return *logTime; 1063 } 1064 } 1065 } 1066 } 1067 BMCWEB_LOG_DEBUG << "failed to find log timestamp"; 1068 1069 return std::string(); 1070 } 1071 1072 class CPULogEntry : public Node 1073 { 1074 public: 1075 CPULogEntry(CrowApp &app) : 1076 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/", 1077 std::string()) 1078 { 1079 entityPrivileges = { 1080 {boost::beast::http::verb::get, {{"Login"}}}, 1081 {boost::beast::http::verb::head, {{"Login"}}}, 1082 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1083 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1084 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1085 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1086 } 1087 1088 private: 1089 void doGet(crow::Response &res, const crow::Request &req, 1090 const std::vector<std::string> ¶ms) override 1091 { 1092 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1093 if (params.size() != 1) 1094 { 1095 messages::internalError(asyncResp->res); 1096 return; 1097 } 1098 const uint8_t logId = std::atoi(params[0].c_str()); 1099 auto getStoredLogCallback = [asyncResp, logId]( 1100 const boost::system::error_code ec, 1101 const std::variant<std::string> &resp) { 1102 if (ec) 1103 { 1104 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 1105 messages::internalError(asyncResp->res); 1106 return; 1107 } 1108 const std::string *log = std::get_if<std::string>(&resp); 1109 if (log == nullptr) 1110 { 1111 messages::internalError(asyncResp->res); 1112 return; 1113 } 1114 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 1115 if (j.is_discarded()) 1116 { 1117 messages::internalError(asyncResp->res); 1118 return; 1119 } 1120 std::string t = getLogCreatedTime(j); 1121 asyncResp->res.jsonValue = { 1122 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 1123 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1124 {"@odata.id", 1125 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" + 1126 std::to_string(logId)}, 1127 {"Name", "CPU Debug Log"}, 1128 {"Id", logId}, 1129 {"EntryType", "Oem"}, 1130 {"OemRecordFormat", "Intel CPU Log"}, 1131 {"Oem", {{"Intel", std::move(j)}}}, 1132 {"Created", std::move(t)}}; 1133 }; 1134 crow::connections::systemBus->async_method_call( 1135 std::move(getStoredLogCallback), cpuLogObject, 1136 cpuLogPath + std::string("/") + std::to_string(logId), 1137 "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log"); 1138 } 1139 }; 1140 1141 class ImmediateCPULog : public Node 1142 { 1143 public: 1144 ImmediateCPULog(CrowApp &app) : 1145 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 1146 "CpuLog.Immediate/") 1147 { 1148 entityPrivileges = { 1149 {boost::beast::http::verb::get, {{"Login"}}}, 1150 {boost::beast::http::verb::head, {{"Login"}}}, 1151 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1152 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1153 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1154 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1155 } 1156 1157 private: 1158 void doPost(crow::Response &res, const crow::Request &req, 1159 const std::vector<std::string> ¶ms) override 1160 { 1161 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1162 static std::unique_ptr<sdbusplus::bus::match::match> 1163 immediateLogMatcher; 1164 1165 // Only allow one Immediate Log request at a time 1166 if (immediateLogMatcher != nullptr) 1167 { 1168 asyncResp->res.addHeader("Retry-After", "30"); 1169 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 1170 return; 1171 } 1172 // Make this static so it survives outside this method 1173 static boost::asio::deadline_timer timeout(*req.ioService); 1174 1175 timeout.expires_from_now(boost::posix_time::seconds(30)); 1176 timeout.async_wait([asyncResp](const boost::system::error_code &ec) { 1177 immediateLogMatcher = nullptr; 1178 if (ec) 1179 { 1180 // operation_aborted is expected if timer is canceled before 1181 // completion. 1182 if (ec != boost::asio::error::operation_aborted) 1183 { 1184 BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 1185 } 1186 return; 1187 } 1188 BMCWEB_LOG_ERROR << "Timed out waiting for immediate log"; 1189 1190 messages::internalError(asyncResp->res); 1191 }); 1192 1193 auto immediateLogMatcherCallback = [asyncResp]( 1194 sdbusplus::message::message &m) { 1195 BMCWEB_LOG_DEBUG << "Immediate log available match fired"; 1196 boost::system::error_code ec; 1197 timeout.cancel(ec); 1198 if (ec) 1199 { 1200 BMCWEB_LOG_ERROR << "error canceling timer " << ec; 1201 } 1202 sdbusplus::message::object_path objPath; 1203 boost::container::flat_map< 1204 std::string, boost::container::flat_map< 1205 std::string, std::variant<std::string>>> 1206 interfacesAdded; 1207 m.read(objPath, interfacesAdded); 1208 const std::string *log = std::get_if<std::string>( 1209 &interfacesAdded[cpuLogInterface]["Log"]); 1210 if (log == nullptr) 1211 { 1212 messages::internalError(asyncResp->res); 1213 // Careful with immediateLogMatcher. It is a unique_ptr to the 1214 // match object inside which this lambda is executing. Once it 1215 // is set to nullptr, the match object will be destroyed and the 1216 // lambda will lose its context, including res, so it needs to 1217 // be the last thing done. 1218 immediateLogMatcher = nullptr; 1219 return; 1220 } 1221 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 1222 if (j.is_discarded()) 1223 { 1224 messages::internalError(asyncResp->res); 1225 // Careful with immediateLogMatcher. It is a unique_ptr to the 1226 // match object inside which this lambda is executing. Once it 1227 // is set to nullptr, the match object will be destroyed and the 1228 // lambda will lose its context, including res, so it needs to 1229 // be the last thing done. 1230 immediateLogMatcher = nullptr; 1231 return; 1232 } 1233 std::string t = getLogCreatedTime(j); 1234 asyncResp->res.jsonValue = { 1235 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 1236 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1237 {"Name", "CPU Debug Log"}, 1238 {"EntryType", "Oem"}, 1239 {"OemRecordFormat", "Intel CPU Log"}, 1240 {"Oem", {{"Intel", std::move(j)}}}, 1241 {"Created", std::move(t)}}; 1242 // Careful with immediateLogMatcher. It is a unique_ptr to the 1243 // match object inside which this lambda is executing. Once it is 1244 // set to nullptr, the match object will be destroyed and the lambda 1245 // will lose its context, including res, so it needs to be the last 1246 // thing done. 1247 immediateLogMatcher = nullptr; 1248 }; 1249 immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>( 1250 *crow::connections::systemBus, 1251 sdbusplus::bus::match::rules::interfacesAdded() + 1252 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath), 1253 std::move(immediateLogMatcherCallback)); 1254 1255 auto generateImmediateLogCallback = 1256 [asyncResp](const boost::system::error_code ec, 1257 const std::string &resp) { 1258 if (ec) 1259 { 1260 if (ec.value() == 1261 boost::system::errc::operation_not_supported) 1262 { 1263 messages::resourceInStandby(asyncResp->res); 1264 } 1265 else 1266 { 1267 messages::internalError(asyncResp->res); 1268 } 1269 boost::system::error_code timeoutec; 1270 timeout.cancel(timeoutec); 1271 if (timeoutec) 1272 { 1273 BMCWEB_LOG_ERROR << "error canceling timer " 1274 << timeoutec; 1275 } 1276 immediateLogMatcher = nullptr; 1277 return; 1278 } 1279 }; 1280 crow::connections::systemBus->async_method_call( 1281 std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath, 1282 cpuLogImmediateInterface, "GenerateImmediateLog"); 1283 } 1284 }; 1285 1286 class SendRawPECI : public Node 1287 { 1288 public: 1289 SendRawPECI(CrowApp &app) : 1290 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 1291 "CpuLog.SendRawPeci/") 1292 { 1293 entityPrivileges = { 1294 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 1295 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 1296 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1297 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1298 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1299 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1300 } 1301 1302 private: 1303 void doPost(crow::Response &res, const crow::Request &req, 1304 const std::vector<std::string> ¶ms) override 1305 { 1306 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1307 uint8_t clientAddress = 0; 1308 uint8_t readLength = 0; 1309 std::vector<uint8_t> peciCommand; 1310 if (!json_util::readJson(req, res, "ClientAddress", clientAddress, 1311 "ReadLength", readLength, "PECICommand", 1312 peciCommand)) 1313 { 1314 return; 1315 } 1316 1317 // Callback to return the Raw PECI response 1318 auto sendRawPECICallback = 1319 [asyncResp](const boost::system::error_code ec, 1320 const std::vector<uint8_t> &resp) { 1321 if (ec) 1322 { 1323 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: " 1324 << ec.message(); 1325 messages::internalError(asyncResp->res); 1326 return; 1327 } 1328 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"}, 1329 {"PECIResponse", resp}}; 1330 }; 1331 // Call the SendRawPECI command with the provided data 1332 crow::connections::systemBus->async_method_call( 1333 std::move(sendRawPECICallback), cpuLogObject, cpuLogPath, 1334 cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength, 1335 peciCommand); 1336 } 1337 }; 1338 1339 } // namespace redfish 1340