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