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 26 namespace redfish 27 { 28 29 constexpr char const *cpuLogObject = "com.intel.CpuDebugLog"; 30 constexpr char const *cpuLogPath = "/com/intel/CpuDebugLog"; 31 constexpr char const *cpuLogImmediatePath = "/com/intel/CpuDebugLog/Immediate"; 32 constexpr char const *cpuLogInterface = "com.intel.CpuDebugLog"; 33 constexpr char const *cpuLogImmediateInterface = 34 "com.intel.CpuDebugLog.Immediate"; 35 constexpr char const *cpuLogRawPECIInterface = 36 "com.intel.CpuDebugLog.SendRawPeci"; 37 38 namespace fs = std::filesystem; 39 40 static int getJournalMetadata(sd_journal *journal, 41 const boost::string_view &field, 42 boost::string_view &contents) 43 { 44 const char *data = nullptr; 45 size_t length = 0; 46 int ret = 0; 47 // Get the metadata from the requested field of the journal entry 48 ret = sd_journal_get_data(journal, field.data(), (const void **)&data, 49 &length); 50 if (ret < 0) 51 { 52 return ret; 53 } 54 contents = boost::string_view(data, length); 55 // Only use the content after the "=" character. 56 contents.remove_prefix(std::min(contents.find("=") + 1, contents.size())); 57 return ret; 58 } 59 60 static int getJournalMetadata(sd_journal *journal, 61 const boost::string_view &field, const int &base, 62 int &contents) 63 { 64 int ret = 0; 65 boost::string_view metadata; 66 // Get the metadata from the requested field of the journal entry 67 ret = getJournalMetadata(journal, field, metadata); 68 if (ret < 0) 69 { 70 return ret; 71 } 72 contents = strtol(metadata.data(), nullptr, base); 73 return ret; 74 } 75 76 static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp) 77 { 78 int ret = 0; 79 uint64_t timestamp = 0; 80 ret = sd_journal_get_realtime_usec(journal, ×tamp); 81 if (ret < 0) 82 { 83 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 84 << strerror(-ret); 85 return false; 86 } 87 time_t t = 88 static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s 89 struct tm *loctime = localtime(&t); 90 char entryTime[64] = {}; 91 if (NULL != loctime) 92 { 93 strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime); 94 } 95 // Insert the ':' into the timezone 96 boost::string_view t1(entryTime); 97 boost::string_view t2(entryTime); 98 if (t1.size() > 2 && t2.size() > 2) 99 { 100 t1.remove_suffix(2); 101 t2.remove_prefix(t2.size() - 2); 102 } 103 entryTimestamp = t1.to_string() + ":" + t2.to_string(); 104 return true; 105 } 106 107 static bool getSkipParam(crow::Response &res, const crow::Request &req, 108 long &skip) 109 { 110 char *skipParam = req.urlParams.get("$skip"); 111 if (skipParam != nullptr) 112 { 113 char *ptr = nullptr; 114 skip = std::strtol(skipParam, &ptr, 10); 115 if (*skipParam == '\0' || *ptr != '\0') 116 { 117 118 messages::queryParameterValueTypeError(res, std::string(skipParam), 119 "$skip"); 120 return false; 121 } 122 if (skip < 0) 123 { 124 125 messages::queryParameterOutOfRange(res, std::to_string(skip), 126 "$skip", "greater than 0"); 127 return false; 128 } 129 } 130 return true; 131 } 132 133 static constexpr const long maxEntriesPerPage = 1000; 134 static bool getTopParam(crow::Response &res, const crow::Request &req, 135 long &top) 136 { 137 char *topParam = req.urlParams.get("$top"); 138 if (topParam != nullptr) 139 { 140 char *ptr = nullptr; 141 top = std::strtol(topParam, &ptr, 10); 142 if (*topParam == '\0' || *ptr != '\0') 143 { 144 messages::queryParameterValueTypeError(res, std::string(topParam), 145 "$top"); 146 return false; 147 } 148 if (top < 1 || top > maxEntriesPerPage) 149 { 150 151 messages::queryParameterOutOfRange( 152 res, std::to_string(top), "$top", 153 "1-" + std::to_string(maxEntriesPerPage)); 154 return false; 155 } 156 } 157 return true; 158 } 159 160 static bool getUniqueEntryID(sd_journal *journal, std::string &entryID) 161 { 162 int ret = 0; 163 static uint64_t prevTs = 0; 164 static int index = 0; 165 // Get the entry timestamp 166 uint64_t curTs = 0; 167 ret = sd_journal_get_realtime_usec(journal, &curTs); 168 if (ret < 0) 169 { 170 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 171 << strerror(-ret); 172 return false; 173 } 174 // If the timestamp isn't unique, increment the index 175 if (curTs == prevTs) 176 { 177 index++; 178 } 179 else 180 { 181 // Otherwise, reset it 182 index = 0; 183 } 184 // Save the timestamp 185 prevTs = curTs; 186 187 entryID = std::to_string(curTs); 188 if (index > 0) 189 { 190 entryID += "_" + std::to_string(index); 191 } 192 return true; 193 } 194 195 static bool getTimestampFromID(crow::Response &res, const std::string &entryID, 196 uint64_t ×tamp, uint16_t &index) 197 { 198 if (entryID.empty()) 199 { 200 return false; 201 } 202 // Convert the unique ID back to a timestamp to find the entry 203 boost::string_view tsStr(entryID); 204 205 auto underscorePos = tsStr.find("_"); 206 if (underscorePos != tsStr.npos) 207 { 208 // Timestamp has an index 209 tsStr.remove_suffix(tsStr.size() - underscorePos); 210 boost::string_view indexStr(entryID); 211 indexStr.remove_prefix(underscorePos + 1); 212 std::size_t pos; 213 try 214 { 215 index = std::stoul(indexStr.to_string(), &pos); 216 } 217 catch (std::invalid_argument) 218 { 219 messages::resourceMissingAtURI(res, entryID); 220 return false; 221 } 222 catch (std::out_of_range) 223 { 224 messages::resourceMissingAtURI(res, entryID); 225 return false; 226 } 227 if (pos != indexStr.size()) 228 { 229 messages::resourceMissingAtURI(res, entryID); 230 return false; 231 } 232 } 233 // Timestamp has no index 234 std::size_t pos; 235 try 236 { 237 timestamp = std::stoull(tsStr.to_string(), &pos); 238 } 239 catch (std::invalid_argument) 240 { 241 messages::resourceMissingAtURI(res, entryID); 242 return false; 243 } 244 catch (std::out_of_range) 245 { 246 messages::resourceMissingAtURI(res, entryID); 247 return false; 248 } 249 if (pos != tsStr.size()) 250 { 251 messages::resourceMissingAtURI(res, entryID); 252 return false; 253 } 254 return true; 255 } 256 257 class SystemLogServiceCollection : public Node 258 { 259 public: 260 template <typename CrowApp> 261 SystemLogServiceCollection(CrowApp &app) : 262 Node(app, "/redfish/v1/Systems/<str>/LogServices/", std::string()) 263 { 264 entityPrivileges = { 265 {boost::beast::http::verb::get, {{"Login"}}}, 266 {boost::beast::http::verb::head, {{"Login"}}}, 267 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 268 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 269 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 270 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 271 } 272 273 private: 274 /** 275 * Functions triggers appropriate requests on DBus 276 */ 277 void doGet(crow::Response &res, const crow::Request &req, 278 const std::vector<std::string> ¶ms) override 279 { 280 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 281 const std::string &name = params[0]; 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/" + name + "/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({{"@odata.id", "/redfish/v1/Systems/" + name + 296 "/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/<str>/LogServices/EventLog/", 308 std::string()) 309 { 310 entityPrivileges = { 311 {boost::beast::http::verb::get, {{"Login"}}}, 312 {boost::beast::http::verb::head, {{"Login"}}}, 313 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 314 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 315 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 316 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 317 } 318 319 private: 320 void doGet(crow::Response &res, const crow::Request &req, 321 const std::vector<std::string> ¶ms) override 322 { 323 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 324 325 const std::string &name = params[0]; 326 asyncResp->res.jsonValue["@odata.id"] = 327 "/redfish/v1/Systems/" + name + "/LogServices/EventLog"; 328 asyncResp->res.jsonValue["@odata.type"] = 329 "#LogService.v1_1_0.LogService"; 330 asyncResp->res.jsonValue["@odata.context"] = 331 "/redfish/v1/$metadata#LogService.LogService"; 332 asyncResp->res.jsonValue["Name"] = "Event Log Service"; 333 asyncResp->res.jsonValue["Description"] = "System Event Log Service"; 334 asyncResp->res.jsonValue["Id"] = "Event Log"; 335 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 336 asyncResp->res.jsonValue["Entries"] = { 337 {"@odata.id", 338 "/redfish/v1/Systems/" + name + "/LogServices/EventLog/Entries"}}; 339 } 340 }; 341 342 static int fillEventLogEntryJson(const std::string &systemName, 343 const std::string &bmcLogEntryID, 344 const boost::string_view &messageID, 345 sd_journal *journal, 346 nlohmann::json &bmcLogEntryJson) 347 { 348 // Get the Log Entry contents 349 int ret = 0; 350 351 boost::string_view msg; 352 ret = getJournalMetadata(journal, "MESSAGE", msg); 353 if (ret < 0) 354 { 355 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 356 return 1; 357 } 358 359 // Get the severity from the PRIORITY field 360 int severity = 8; // Default to an invalid priority 361 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 362 if (ret < 0) 363 { 364 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 365 return 1; 366 } 367 368 // Get the MessageArgs from the journal entry by finding all of the 369 // REDFISH_MESSAGE_ARG_x fields 370 const void *data; 371 size_t length; 372 std::vector<std::string> messageArgs; 373 SD_JOURNAL_FOREACH_DATA(journal, data, length) 374 { 375 boost::string_view field(static_cast<const char *>(data), length); 376 if (field.starts_with("REDFISH_MESSAGE_ARG_")) 377 { 378 // Get the Arg number from the field name 379 field.remove_prefix(sizeof("REDFISH_MESSAGE_ARG_") - 1); 380 if (field.empty()) 381 { 382 continue; 383 } 384 int argNum = std::strtoul(field.data(), nullptr, 10); 385 if (argNum == 0) 386 { 387 continue; 388 } 389 // Get the Arg value after the "=" character. 390 field.remove_prefix(std::min(field.find("=") + 1, field.size())); 391 // Make sure we have enough space in messageArgs 392 if (argNum > messageArgs.size()) 393 { 394 messageArgs.resize(argNum); 395 } 396 messageArgs[argNum - 1] = field.to_string(); 397 } 398 } 399 400 // Get the Created time from the timestamp 401 std::string entryTimeStr; 402 if (!getEntryTimestamp(journal, entryTimeStr)) 403 { 404 return 1; 405 } 406 407 // Fill in the log entry with the gathered data 408 bmcLogEntryJson = { 409 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 410 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 411 {"@odata.id", "/redfish/v1/Systems/" + systemName + 412 "/LogServices/EventLog/Entries/" + bmcLogEntryID}, 413 {"Name", "System Event Log Entry"}, 414 {"Id", bmcLogEntryID}, 415 {"Message", msg}, 416 {"MessageId", messageID}, 417 {"MessageArgs", std::move(messageArgs)}, 418 {"EntryType", "Event"}, 419 {"Severity", 420 severity <= 2 ? "Critical" 421 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""}, 422 {"Created", std::move(entryTimeStr)}}; 423 return 0; 424 } 425 426 class EventLogEntryCollection : public Node 427 { 428 public: 429 template <typename CrowApp> 430 EventLogEntryCollection(CrowApp &app) : 431 Node(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/", 432 std::string()) 433 { 434 entityPrivileges = { 435 {boost::beast::http::verb::get, {{"Login"}}}, 436 {boost::beast::http::verb::head, {{"Login"}}}, 437 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 438 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 439 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 440 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 441 } 442 443 private: 444 void doGet(crow::Response &res, const crow::Request &req, 445 const std::vector<std::string> ¶ms) override 446 { 447 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 448 long skip = 0; 449 long top = maxEntriesPerPage; // Show max entries by default 450 if (!getSkipParam(asyncResp->res, req, skip)) 451 { 452 return; 453 } 454 if (!getTopParam(asyncResp->res, req, top)) 455 { 456 return; 457 } 458 const std::string &name = params[0]; 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/" + name + "/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 uint64_t entryCount = 0; 486 SD_JOURNAL_FOREACH(journal.get()) 487 { 488 // Look for only journal entries that contain a REDFISH_MESSAGE_ID 489 // field 490 boost::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(name, 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/<str>/LogServices/EventLog/Entries/<str>/", 537 std::string(), 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() != 2) 554 { 555 messages::internalError(asyncResp->res); 556 return; 557 } 558 const std::string &name = params[0]; 559 const std::string &entryID = params[1]; 560 // Convert the unique ID back to a timestamp to find the entry 561 uint64_t ts = 0; 562 uint16_t index = 0; 563 if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 564 { 565 return; 566 } 567 568 sd_journal *journalTmp = nullptr; 569 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 570 if (ret < 0) 571 { 572 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 573 messages::internalError(asyncResp->res); 574 return; 575 } 576 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 577 journalTmp, sd_journal_close); 578 journalTmp = nullptr; 579 // Go to the timestamp in the log and move to the entry at the index 580 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 581 for (int i = 0; i <= index; i++) 582 { 583 sd_journal_next(journal.get()); 584 } 585 // Confirm that the entry ID matches what was requested 586 std::string idStr; 587 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID) 588 { 589 messages::resourceMissingAtURI(asyncResp->res, entryID); 590 return; 591 } 592 593 // only use journal entries that contain a REDFISH_MESSAGE_ID 594 // field 595 boost::string_view messageID; 596 ret = 597 getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", messageID); 598 if (ret < 0) 599 { 600 messages::resourceNotFound(asyncResp->res, "LogEntry", name); 601 return; 602 } 603 604 if (fillEventLogEntryJson(name, entryID, messageID, journal.get(), 605 asyncResp->res.jsonValue) != 0) 606 { 607 messages::internalError(asyncResp->res); 608 return; 609 } 610 } 611 }; 612 613 class BMCLogServiceCollection : public Node 614 { 615 public: 616 template <typename CrowApp> 617 BMCLogServiceCollection(CrowApp &app) : 618 Node(app, "/redfish/v1/Managers/bmc/LogServices/") 619 { 620 entityPrivileges = { 621 {boost::beast::http::verb::get, {{"Login"}}}, 622 {boost::beast::http::verb::head, {{"Login"}}}, 623 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 624 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 625 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 626 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 627 } 628 629 private: 630 /** 631 * Functions triggers appropriate requests on DBus 632 */ 633 void doGet(crow::Response &res, const crow::Request &req, 634 const std::vector<std::string> ¶ms) override 635 { 636 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 637 // Collections don't include the static data added by SubRoute because 638 // it has a duplicate entry for members 639 asyncResp->res.jsonValue["@odata.type"] = 640 "#LogServiceCollection.LogServiceCollection"; 641 asyncResp->res.jsonValue["@odata.context"] = 642 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; 643 asyncResp->res.jsonValue["@odata.id"] = 644 "/redfish/v1/Managers/bmc/LogServices"; 645 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; 646 asyncResp->res.jsonValue["Description"] = 647 "Collection of LogServices for this Manager"; 648 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 649 logServiceArray = nlohmann::json::array(); 650 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 651 logServiceArray.push_back( 652 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}}); 653 #endif 654 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG 655 logServiceArray.push_back( 656 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/CpuLog"}}); 657 #endif 658 asyncResp->res.jsonValue["Members@odata.count"] = 659 logServiceArray.size(); 660 } 661 }; 662 663 class BMCJournalLogService : public Node 664 { 665 public: 666 template <typename CrowApp> 667 BMCJournalLogService(CrowApp &app) : 668 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 669 { 670 entityPrivileges = { 671 {boost::beast::http::verb::get, {{"Login"}}}, 672 {boost::beast::http::verb::head, {{"Login"}}}, 673 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 674 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 675 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 676 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 677 } 678 679 private: 680 void doGet(crow::Response &res, const crow::Request &req, 681 const std::vector<std::string> ¶ms) override 682 { 683 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 684 asyncResp->res.jsonValue["@odata.type"] = 685 "#LogService.v1_1_0.LogService"; 686 asyncResp->res.jsonValue["@odata.id"] = 687 "/redfish/v1/Managers/bmc/LogServices/Journal"; 688 asyncResp->res.jsonValue["@odata.context"] = 689 "/redfish/v1/$metadata#LogService.LogService"; 690 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 691 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 692 asyncResp->res.jsonValue["Id"] = "BMC Journal"; 693 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 694 } 695 }; 696 697 static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID, 698 sd_journal *journal, 699 nlohmann::json &bmcJournalLogEntryJson) 700 { 701 // Get the Log Entry contents 702 int ret = 0; 703 704 boost::string_view msg; 705 ret = getJournalMetadata(journal, "MESSAGE", msg); 706 if (ret < 0) 707 { 708 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 709 return 1; 710 } 711 712 // Get the severity from the PRIORITY field 713 int severity = 8; // Default to an invalid priority 714 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 715 if (ret < 0) 716 { 717 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 718 return 1; 719 } 720 721 // Get the Created time from the timestamp 722 std::string entryTimeStr; 723 if (!getEntryTimestamp(journal, entryTimeStr)) 724 { 725 return 1; 726 } 727 728 // Fill in the log entry with the gathered data 729 bmcJournalLogEntryJson = { 730 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 731 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 732 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + 733 bmcJournalLogEntryID}, 734 {"Name", "BMC Journal Entry"}, 735 {"Id", bmcJournalLogEntryID}, 736 {"Message", msg}, 737 {"EntryType", "Oem"}, 738 {"Severity", 739 severity <= 2 ? "Critical" 740 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""}, 741 {"OemRecordFormat", "Intel BMC Journal Entry"}, 742 {"Created", std::move(entryTimeStr)}}; 743 return 0; 744 } 745 746 class BMCJournalLogEntryCollection : public Node 747 { 748 public: 749 template <typename CrowApp> 750 BMCJournalLogEntryCollection(CrowApp &app) : 751 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 752 { 753 entityPrivileges = { 754 {boost::beast::http::verb::get, {{"Login"}}}, 755 {boost::beast::http::verb::head, {{"Login"}}}, 756 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 757 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 758 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 759 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 760 } 761 762 private: 763 void doGet(crow::Response &res, const crow::Request &req, 764 const std::vector<std::string> ¶ms) override 765 { 766 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 767 static constexpr const long maxEntriesPerPage = 1000; 768 long skip = 0; 769 long top = maxEntriesPerPage; // Show max entries by default 770 if (!getSkipParam(asyncResp->res, req, skip)) 771 { 772 return; 773 } 774 if (!getTopParam(asyncResp->res, req, top)) 775 { 776 return; 777 } 778 // Collections don't include the static data added by SubRoute because 779 // it has a duplicate entry for members 780 asyncResp->res.jsonValue["@odata.type"] = 781 "#LogEntryCollection.LogEntryCollection"; 782 asyncResp->res.jsonValue["@odata.id"] = 783 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 784 asyncResp->res.jsonValue["@odata.context"] = 785 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 786 asyncResp->res.jsonValue["@odata.id"] = 787 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 788 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 789 asyncResp->res.jsonValue["Description"] = 790 "Collection of BMC Journal Entries"; 791 asyncResp->res.jsonValue["@odata.id"] = 792 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries"; 793 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 794 logEntryArray = nlohmann::json::array(); 795 796 // Go through the journal and use the timestamp to create a unique ID 797 // for each entry 798 sd_journal *journalTmp = nullptr; 799 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 800 if (ret < 0) 801 { 802 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 803 messages::internalError(asyncResp->res); 804 return; 805 } 806 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 807 journalTmp, sd_journal_close); 808 journalTmp = nullptr; 809 uint64_t entryCount = 0; 810 SD_JOURNAL_FOREACH(journal.get()) 811 { 812 entryCount++; 813 // Handle paging using skip (number of entries to skip from the 814 // start) and top (number of entries to display) 815 if (entryCount <= skip || entryCount > skip + top) 816 { 817 continue; 818 } 819 820 std::string idStr; 821 if (!getUniqueEntryID(journal.get(), idStr)) 822 { 823 continue; 824 } 825 826 logEntryArray.push_back({}); 827 nlohmann::json &bmcJournalLogEntry = logEntryArray.back(); 828 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 829 bmcJournalLogEntry) != 0) 830 { 831 messages::internalError(asyncResp->res); 832 return; 833 } 834 } 835 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 836 if (skip + top < entryCount) 837 { 838 asyncResp->res.jsonValue["Members@odata.nextLink"] = 839 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 840 std::to_string(skip + top); 841 } 842 } 843 }; 844 845 class BMCJournalLogEntry : public Node 846 { 847 public: 848 BMCJournalLogEntry(CrowApp &app) : 849 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/", 850 std::string()) 851 { 852 entityPrivileges = { 853 {boost::beast::http::verb::get, {{"Login"}}}, 854 {boost::beast::http::verb::head, {{"Login"}}}, 855 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 856 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 857 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 858 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 859 } 860 861 private: 862 void doGet(crow::Response &res, const crow::Request &req, 863 const std::vector<std::string> ¶ms) override 864 { 865 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 866 if (params.size() != 1) 867 { 868 messages::internalError(asyncResp->res); 869 return; 870 } 871 const std::string &entryID = params[0]; 872 // Convert the unique ID back to a timestamp to find the entry 873 uint64_t ts = 0; 874 uint16_t index = 0; 875 if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 876 { 877 return; 878 } 879 880 sd_journal *journalTmp = nullptr; 881 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 882 if (ret < 0) 883 { 884 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 885 messages::internalError(asyncResp->res); 886 return; 887 } 888 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 889 journalTmp, sd_journal_close); 890 journalTmp = nullptr; 891 // Go to the timestamp in the log and move to the entry at the index 892 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 893 for (int i = 0; i <= index; i++) 894 { 895 sd_journal_next(journal.get()); 896 } 897 // Confirm that the entry ID matches what was requested 898 std::string idStr; 899 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID) 900 { 901 messages::resourceMissingAtURI(asyncResp->res, entryID); 902 return; 903 } 904 905 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 906 asyncResp->res.jsonValue) != 0) 907 { 908 messages::internalError(asyncResp->res); 909 return; 910 } 911 } 912 }; 913 914 class CPULogService : public Node 915 { 916 public: 917 template <typename CrowApp> 918 CPULogService(CrowApp &app) : 919 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/") 920 { 921 entityPrivileges = { 922 {boost::beast::http::verb::get, {{"Login"}}}, 923 {boost::beast::http::verb::head, {{"Login"}}}, 924 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 925 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 926 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 927 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 928 } 929 930 private: 931 /** 932 * Functions triggers appropriate requests on DBus 933 */ 934 void doGet(crow::Response &res, const crow::Request &req, 935 const std::vector<std::string> ¶ms) override 936 { 937 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 938 // Copy over the static data to include the entries added by SubRoute 939 asyncResp->res.jsonValue["@odata.id"] = 940 "/redfish/v1/Managers/bmc/LogServices/CpuLog"; 941 asyncResp->res.jsonValue["@odata.type"] = 942 "#LogService.v1_1_0.LogService"; 943 asyncResp->res.jsonValue["@odata.context"] = 944 "/redfish/v1/$metadata#LogService.LogService"; 945 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service"; 946 asyncResp->res.jsonValue["Description"] = "CPU Log Service"; 947 asyncResp->res.jsonValue["Id"] = "CPU Log"; 948 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 949 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 950 asyncResp->res.jsonValue["Actions"] = { 951 {"Oem", 952 {{"#CpuLog.Immediate", 953 {{"target", "/redfish/v1/Managers/bmc/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/Managers/bmc/LogServices/CpuLog/Actions/" 960 "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/Managers/bmc/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/Managers/bmc/LogServices/CpuLog/Entries"; 1009 asyncResp->res.jsonValue["@odata.context"] = 1010 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 1011 asyncResp->res.jsonValue["@odata.id"] = 1012 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries"; 1013 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries"; 1014 asyncResp->res.jsonValue["Description"] = 1015 "Collection of CPU Log Entries"; 1016 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1017 logEntryArray = nlohmann::json::array(); 1018 for (const std::string &objpath : resp) 1019 { 1020 // Don't list the immediate log 1021 if (objpath.compare(cpuLogImmediatePath) == 0) 1022 { 1023 continue; 1024 } 1025 std::size_t lastPos = objpath.rfind("/"); 1026 if (lastPos != std::string::npos) 1027 { 1028 logEntryArray.push_back( 1029 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/" 1030 "CpuLog/Entries/" + 1031 objpath.substr(lastPos + 1)}}); 1032 } 1033 } 1034 asyncResp->res.jsonValue["Members@odata.count"] = 1035 logEntryArray.size(); 1036 }; 1037 crow::connections::systemBus->async_method_call( 1038 std::move(getLogEntriesCallback), 1039 "xyz.openbmc_project.ObjectMapper", 1040 "/xyz/openbmc_project/object_mapper", 1041 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 1042 std::array<const char *, 1>{cpuLogInterface}); 1043 } 1044 }; 1045 1046 std::string getLogCreatedTime(const nlohmann::json &cpuLog) 1047 { 1048 nlohmann::json::const_iterator metaIt = cpuLog.find("metadata"); 1049 if (metaIt != cpuLog.end()) 1050 { 1051 nlohmann::json::const_iterator tsIt = metaIt->find("timestamp"); 1052 if (tsIt != metaIt->end()) 1053 { 1054 const std::string *logTime = tsIt->get_ptr<const std::string *>(); 1055 if (logTime != nullptr) 1056 { 1057 return *logTime; 1058 } 1059 } 1060 } 1061 BMCWEB_LOG_DEBUG << "failed to find log timestamp"; 1062 1063 return std::string(); 1064 } 1065 1066 class CPULogEntry : public Node 1067 { 1068 public: 1069 CPULogEntry(CrowApp &app) : 1070 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/", 1071 std::string()) 1072 { 1073 entityPrivileges = { 1074 {boost::beast::http::verb::get, {{"Login"}}}, 1075 {boost::beast::http::verb::head, {{"Login"}}}, 1076 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1077 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1078 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1079 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1080 } 1081 1082 private: 1083 void doGet(crow::Response &res, const crow::Request &req, 1084 const std::vector<std::string> ¶ms) override 1085 { 1086 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1087 if (params.size() != 1) 1088 { 1089 messages::internalError(asyncResp->res); 1090 return; 1091 } 1092 const uint8_t logId = std::atoi(params[0].c_str()); 1093 auto getStoredLogCallback = 1094 [asyncResp, 1095 logId](const boost::system::error_code ec, 1096 const sdbusplus::message::variant<std::string> &resp) { 1097 if (ec) 1098 { 1099 BMCWEB_LOG_DEBUG << "failed to get log ec: " 1100 << ec.message(); 1101 messages::internalError(asyncResp->res); 1102 return; 1103 } 1104 const std::string *log = 1105 sdbusplus::message::variant_ns::get_if<std::string>(&resp); 1106 if (log == nullptr) 1107 { 1108 messages::internalError(asyncResp->res); 1109 return; 1110 } 1111 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 1112 if (j.is_discarded()) 1113 { 1114 messages::internalError(asyncResp->res); 1115 return; 1116 } 1117 std::string t = getLogCreatedTime(j); 1118 asyncResp->res.jsonValue = { 1119 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 1120 {"@odata.context", 1121 "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1122 {"@odata.id", 1123 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" + 1124 std::to_string(logId)}, 1125 {"Name", "CPU Debug Log"}, 1126 {"Id", logId}, 1127 {"EntryType", "Oem"}, 1128 {"OemRecordFormat", "Intel CPU Log"}, 1129 {"Oem", {{"Intel", std::move(j)}}}, 1130 {"Created", std::move(t)}}; 1131 }; 1132 crow::connections::systemBus->async_method_call( 1133 std::move(getStoredLogCallback), cpuLogObject, 1134 cpuLogPath + std::string("/") + std::to_string(logId), 1135 "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log"); 1136 } 1137 }; 1138 1139 class ImmediateCPULog : public Node 1140 { 1141 public: 1142 ImmediateCPULog(CrowApp &app) : 1143 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 1144 "CpuLog.Immediate/") 1145 { 1146 entityPrivileges = { 1147 {boost::beast::http::verb::get, {{"Login"}}}, 1148 {boost::beast::http::verb::head, {{"Login"}}}, 1149 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1150 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1151 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1152 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1153 } 1154 1155 private: 1156 void doPost(crow::Response &res, const crow::Request &req, 1157 const std::vector<std::string> ¶ms) override 1158 { 1159 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1160 static std::unique_ptr<sdbusplus::bus::match::match> 1161 immediateLogMatcher; 1162 1163 // Only allow one Immediate Log request at a time 1164 if (immediateLogMatcher != nullptr) 1165 { 1166 asyncResp->res.addHeader("Retry-After", "30"); 1167 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 1168 return; 1169 } 1170 // Make this static so it survives outside this method 1171 static boost::asio::deadline_timer timeout(*req.ioService); 1172 1173 timeout.expires_from_now(boost::posix_time::seconds(30)); 1174 timeout.async_wait([asyncResp](const boost::system::error_code &ec) { 1175 immediateLogMatcher = nullptr; 1176 if (ec) 1177 { 1178 // operation_aborted is expected if timer is canceled before 1179 // completion. 1180 if (ec != boost::asio::error::operation_aborted) 1181 { 1182 BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 1183 } 1184 return; 1185 } 1186 BMCWEB_LOG_ERROR << "Timed out waiting for immediate log"; 1187 1188 messages::internalError(asyncResp->res); 1189 }); 1190 1191 auto immediateLogMatcherCallback = [asyncResp]( 1192 sdbusplus::message::message &m) { 1193 BMCWEB_LOG_DEBUG << "Immediate log available match fired"; 1194 boost::system::error_code ec; 1195 timeout.cancel(ec); 1196 if (ec) 1197 { 1198 BMCWEB_LOG_ERROR << "error canceling timer " << ec; 1199 } 1200 sdbusplus::message::object_path objPath; 1201 boost::container::flat_map< 1202 std::string, 1203 boost::container::flat_map< 1204 std::string, sdbusplus::message::variant<std::string>>> 1205 interfacesAdded; 1206 m.read(objPath, interfacesAdded); 1207 const std::string *log = 1208 sdbusplus::message::variant_ns::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