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