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 class LogServiceCollection : public Node 41 { 42 public: 43 template <typename CrowApp> 44 LogServiceCollection(CrowApp &app) : 45 Node(app, "/redfish/v1/Managers/bmc/LogServices/") 46 { 47 // Collections use static ID for SubRoute to add to its parent, but only 48 // load dynamic data so the duplicate static members don't get displayed 49 Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices"; 50 entityPrivileges = { 51 {boost::beast::http::verb::get, {{"Login"}}}, 52 {boost::beast::http::verb::head, {{"Login"}}}, 53 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 54 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 55 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 56 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 57 } 58 59 private: 60 /** 61 * Functions triggers appropriate requests on DBus 62 */ 63 void doGet(crow::Response &res, const crow::Request &req, 64 const std::vector<std::string> ¶ms) override 65 { 66 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 67 // Collections don't include the static data added by SubRoute because 68 // it has a duplicate entry for members 69 asyncResp->res.jsonValue["@odata.type"] = 70 "#LogServiceCollection.LogServiceCollection"; 71 asyncResp->res.jsonValue["@odata.context"] = 72 "/redfish/v1/" 73 "$metadata#LogServiceCollection.LogServiceCollection"; 74 asyncResp->res.jsonValue["@odata.id"] = 75 "/redfish/v1/Managers/bmc/LogServices"; 76 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; 77 asyncResp->res.jsonValue["Description"] = 78 "Collection of LogServices for this Manager"; 79 nlohmann::json &logserviceArray = asyncResp->res.jsonValue["Members"]; 80 logserviceArray = nlohmann::json::array(); 81 logserviceArray.push_back( 82 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/BmcLog"}}); 83 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG 84 logserviceArray.push_back( 85 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/CpuLog"}}); 86 #endif 87 asyncResp->res.jsonValue["Members@odata.count"] = 88 logserviceArray.size(); 89 } 90 }; 91 92 class BMCLogService : public Node 93 { 94 public: 95 template <typename CrowApp> 96 BMCLogService(CrowApp &app) : 97 Node(app, "/redfish/v1/Managers/bmc/LogServices/BmcLog/") 98 { 99 // Set the id for SubRoute 100 Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/BmcLog"; 101 entityPrivileges = { 102 {boost::beast::http::verb::get, {{"Login"}}}, 103 {boost::beast::http::verb::head, {{"Login"}}}, 104 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 105 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 106 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 107 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 108 } 109 110 private: 111 void doGet(crow::Response &res, const crow::Request &req, 112 const std::vector<std::string> ¶ms) override 113 { 114 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 115 // Copy over the static data to include the entries added by SubRoute 116 asyncResp->res.jsonValue = Node::json; 117 asyncResp->res.jsonValue["@odata.type"] = 118 "#LogService.v1_1_0.LogService"; 119 asyncResp->res.jsonValue["@odata.context"] = 120 "/redfish/v1/$metadata#LogService.LogService"; 121 asyncResp->res.jsonValue["Name"] = "Open BMC Log Service"; 122 asyncResp->res.jsonValue["Description"] = "BMC Log Service"; 123 asyncResp->res.jsonValue["Id"] = "BMC Log"; 124 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 125 } 126 }; 127 128 static int fillBMCLogEntryJson(const std::string &bmcLogEntryID, 129 sd_journal *journal, 130 nlohmann::json &bmcLogEntryJson) 131 { 132 // Get the Log Entry contents 133 int ret = 0; 134 const char *data = nullptr; 135 size_t length = 0; 136 137 ret = 138 sd_journal_get_data(journal, "MESSAGE", (const void **)&data, &length); 139 if (ret < 0) 140 { 141 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 142 return 1; 143 } 144 boost::string_view msg; 145 msg = boost::string_view(data, length); 146 // Only use the content after the "=" character. 147 msg.remove_prefix(std::min(msg.find("=") + 1, msg.size())); 148 149 // Get the severity from the PRIORITY field 150 boost::string_view priority; 151 int severity = 8; // Default to an invalid priority 152 ret = 153 sd_journal_get_data(journal, "PRIORITY", (const void **)&data, &length); 154 if (ret < 0) 155 { 156 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 157 return 1; 158 } 159 priority = boost::string_view(data, length); 160 // Check length for sanity. Must be a single digit in the form 161 // "PRIORITY=[0-7]" 162 if (priority.size() > sizeof("PRIORITY=0")) 163 { 164 BMCWEB_LOG_ERROR << "Invalid PRIORITY field length"; 165 return 1; 166 } 167 // Only use the content after the "=" character. 168 priority.remove_prefix(std::min(priority.find("=") + 1, priority.size())); 169 severity = strtol(priority.data(), nullptr, 10); 170 171 // Get the Created time from the timestamp 172 // Get the entry timestamp 173 uint64_t timestamp = 0; 174 ret = sd_journal_get_realtime_usec(journal, ×tamp); 175 if (ret < 0) 176 { 177 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 178 << strerror(-ret); 179 } 180 time_t t = 181 static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s 182 struct tm *loctime = localtime(&t); 183 char entryTime[64] = {}; 184 if (NULL != loctime) 185 { 186 strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime); 187 } 188 // Insert the ':' into the timezone 189 boost::string_view t1(entryTime); 190 boost::string_view t2(entryTime); 191 if (t1.size() > 2 && t2.size() > 2) 192 { 193 t1.remove_suffix(2); 194 t2.remove_prefix(t2.size() - 2); 195 } 196 const std::string entryTimeStr(t1.to_string() + ":" + t2.to_string()); 197 198 // Fill in the log entry with the gathered data 199 bmcLogEntryJson = { 200 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 201 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 202 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries/" + 203 bmcLogEntryID}, 204 {"Name", "BMC Journal Entry"}, 205 {"Id", bmcLogEntryID}, 206 {"Message", msg.to_string()}, 207 {"EntryType", "Oem"}, 208 {"Severity", 209 severity <= 2 ? "Critical" 210 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""}, 211 {"OemRecordFormat", "Intel BMC Journal Entry"}, 212 {"Created", std::move(entryTimeStr)}}; 213 return 0; 214 } 215 216 class BMCLogEntryCollection : public Node 217 { 218 public: 219 template <typename CrowApp> 220 BMCLogEntryCollection(CrowApp &app) : 221 Node(app, "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries/") 222 { 223 // Collections use static ID for SubRoute to add to its parent, but only 224 // load dynamic data so the duplicate static members don't get displayed 225 Node::json["@odata.id"] = 226 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries"; 227 entityPrivileges = { 228 {boost::beast::http::verb::get, {{"Login"}}}, 229 {boost::beast::http::verb::head, {{"Login"}}}, 230 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 231 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 232 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 233 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 234 } 235 236 private: 237 void doGet(crow::Response &res, const crow::Request &req, 238 const std::vector<std::string> ¶ms) override 239 { 240 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 241 // Collections don't include the static data added by SubRoute because 242 // it has a duplicate entry for members 243 asyncResp->res.jsonValue["@odata.type"] = 244 "#LogEntryCollection.LogEntryCollection"; 245 asyncResp->res.jsonValue["@odata.context"] = 246 "/redfish/v1/" 247 "$metadata#LogEntryCollection.LogEntryCollection"; 248 asyncResp->res.jsonValue["@odata.id"] = 249 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries"; 250 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 251 asyncResp->res.jsonValue["Description"] = 252 "Collection of BMC Journal Entries"; 253 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 254 logEntryArray = nlohmann::json::array(); 255 256 // Go through the journal and use the timestamp to create a unique ID 257 // for each entry 258 sd_journal *journalTmp = nullptr; 259 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 260 if (ret < 0) 261 { 262 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 263 asyncResp->res.result( 264 boost::beast::http::status::internal_server_error); 265 return; 266 } 267 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 268 journalTmp, sd_journal_close); 269 journalTmp = nullptr; 270 uint64_t prevTs = 0; 271 int index = 0; 272 SD_JOURNAL_FOREACH(journal.get()) 273 { 274 // Get the entry timestamp 275 uint64_t curTs = 0; 276 ret = sd_journal_get_realtime_usec(journal.get(), &curTs); 277 if (ret < 0) 278 { 279 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 280 << strerror(-ret); 281 continue; 282 } 283 // If the timestamp isn't unique, increment the index 284 if (curTs == prevTs) 285 { 286 index++; 287 } 288 else 289 { 290 // Otherwise, reset it 291 index = 0; 292 } 293 // Save the timestamp 294 prevTs = curTs; 295 296 std::string idStr(std::to_string(curTs)); 297 if (index > 0) 298 { 299 idStr += "_" + std::to_string(index); 300 } 301 logEntryArray.push_back({}); 302 nlohmann::json &bmcLogEntry = logEntryArray.back(); 303 if (fillBMCLogEntryJson(idStr, journal.get(), bmcLogEntry) != 0) 304 { 305 asyncResp->res.result( 306 boost::beast::http::status::internal_server_error); 307 return; 308 } 309 } 310 asyncResp->res.jsonValue["Members@odata.count"] = logEntryArray.size(); 311 } 312 }; 313 314 class BMCLogEntry : public Node 315 { 316 public: 317 BMCLogEntry(CrowApp &app) : 318 Node(app, "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries/<str>/", 319 std::string()) 320 { 321 entityPrivileges = { 322 {boost::beast::http::verb::get, {{"Login"}}}, 323 {boost::beast::http::verb::head, {{"Login"}}}, 324 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 325 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 326 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 327 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 328 } 329 330 private: 331 void doGet(crow::Response &res, const crow::Request &req, 332 const std::vector<std::string> ¶ms) override 333 { 334 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 335 if (params.size() != 1) 336 { 337 asyncResp->res.result( 338 boost::beast::http::status::internal_server_error); 339 return; 340 } 341 // Convert the unique ID back to a timestamp to find the entry 342 boost::string_view tsStr(params[0]); 343 boost::string_view indexStr(params[0]); 344 uint64_t ts = 0; 345 uint16_t index = 0; 346 auto underscorePos = tsStr.find("_"); 347 if (underscorePos == tsStr.npos) 348 { 349 // Timestamp has no index 350 ts = strtoull(tsStr.data(), nullptr, 10); 351 } 352 else 353 { 354 // Timestamp has an index 355 tsStr.remove_suffix(tsStr.size() - underscorePos + 1); 356 ts = strtoull(tsStr.data(), nullptr, 10); 357 indexStr.remove_prefix(underscorePos + 1); 358 index = 359 static_cast<uint16_t>(strtoul(indexStr.data(), nullptr, 10)); 360 } 361 362 sd_journal *journalTmp = nullptr; 363 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 364 if (ret < 0) 365 { 366 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 367 asyncResp->res.result( 368 boost::beast::http::status::internal_server_error); 369 return; 370 } 371 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 372 journalTmp, sd_journal_close); 373 journalTmp = nullptr; 374 // Go to the timestamp in the log and move to the entry at the index 375 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 376 for (int i = 0; i <= index; i++) 377 { 378 sd_journal_next(journal.get()); 379 } 380 if (fillBMCLogEntryJson(params[0], journal.get(), 381 asyncResp->res.jsonValue) != 0) 382 { 383 asyncResp->res.result( 384 boost::beast::http::status::internal_server_error); 385 return; 386 } 387 } 388 }; 389 390 class CPULogService : public Node 391 { 392 public: 393 template <typename CrowApp> 394 CPULogService(CrowApp &app) : 395 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/") 396 { 397 // Set the id for SubRoute 398 Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/CpuLog"; 399 entityPrivileges = { 400 {boost::beast::http::verb::get, {{"Login"}}}, 401 {boost::beast::http::verb::head, {{"Login"}}}, 402 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 403 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 404 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 405 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 406 } 407 408 private: 409 /** 410 * Functions triggers appropriate requests on DBus 411 */ 412 void doGet(crow::Response &res, const crow::Request &req, 413 const std::vector<std::string> ¶ms) override 414 { 415 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 416 // Copy over the static data to include the entries added by SubRoute 417 asyncResp->res.jsonValue = Node::json; 418 asyncResp->res.jsonValue["@odata.type"] = 419 "#LogService.v1_1_0.LogService"; 420 asyncResp->res.jsonValue["@odata.context"] = 421 "/redfish/v1/" 422 "$metadata#LogService.LogService"; 423 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service"; 424 asyncResp->res.jsonValue["Description"] = "CPU Log Service"; 425 asyncResp->res.jsonValue["Id"] = "CPU Log"; 426 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 427 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 428 asyncResp->res.jsonValue["Actions"] = { 429 {"Oem", 430 {{"#CpuLog.Immediate", 431 {{"target", 432 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 433 "CpuLog.Immediate"}}}}}}; 434 435 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI 436 asyncResp->res.jsonValue["Actions"]["Oem"].push_back( 437 {"#CpuLog.SendRawPeci", 438 {{"target", 439 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 440 "CpuLog.SendRawPeci"}}}); 441 #endif 442 } 443 }; 444 445 class CPULogEntryCollection : public Node 446 { 447 public: 448 template <typename CrowApp> 449 CPULogEntryCollection(CrowApp &app) : 450 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/") 451 { 452 // Collections use static ID for SubRoute to add to its parent, but only 453 // load dynamic data so the duplicate static members don't get displayed 454 Node::json["@odata.id"] = 455 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries"; 456 entityPrivileges = { 457 {boost::beast::http::verb::get, {{"Login"}}}, 458 {boost::beast::http::verb::head, {{"Login"}}}, 459 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 460 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 461 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 462 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 463 } 464 465 private: 466 /** 467 * Functions triggers appropriate requests on DBus 468 */ 469 void doGet(crow::Response &res, const crow::Request &req, 470 const std::vector<std::string> ¶ms) override 471 { 472 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 473 // Collections don't include the static data added by SubRoute because 474 // it has a duplicate entry for members 475 auto getLogEntriesCallback = [asyncResp]( 476 const boost::system::error_code ec, 477 const std::vector<std::string> &resp) { 478 if (ec) 479 { 480 if (ec.value() != 481 boost::system::errc::no_such_file_or_directory) 482 { 483 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 484 << ec.message(); 485 asyncResp->res.result( 486 boost::beast::http::status::internal_server_error); 487 return; 488 } 489 } 490 asyncResp->res.jsonValue["@odata.type"] = 491 "#LogEntryCollection.LogEntryCollection"; 492 asyncResp->res.jsonValue["@odata.context"] = 493 "/redfish/v1/" 494 "$metadata#LogEntryCollection.LogEntryCollection"; 495 asyncResp->res.jsonValue["@odata.id"] = 496 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries"; 497 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries"; 498 asyncResp->res.jsonValue["Description"] = 499 "Collection of CPU Log Entries"; 500 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 501 logEntryArray = nlohmann::json::array(); 502 for (const std::string &objpath : resp) 503 { 504 // Don't list the immediate log 505 if (objpath.compare(cpuLogImmediatePath) == 0) 506 { 507 continue; 508 } 509 std::size_t lastPos = objpath.rfind("/"); 510 if (lastPos != std::string::npos) 511 { 512 logEntryArray.push_back( 513 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/" 514 "CpuLog/Entries/" + 515 objpath.substr(lastPos + 1)}}); 516 } 517 } 518 asyncResp->res.jsonValue["Members@odata.count"] = 519 logEntryArray.size(); 520 }; 521 crow::connections::systemBus->async_method_call( 522 std::move(getLogEntriesCallback), 523 "xyz.openbmc_project.ObjectMapper", 524 "/xyz/openbmc_project/object_mapper", 525 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 526 std::array<const char *, 1>{cpuLogInterface}); 527 } 528 }; 529 530 std::string getLogCreatedTime(const nlohmann::json &cpuLog) 531 { 532 nlohmann::json::const_iterator metaIt = cpuLog.find("metadata"); 533 if (metaIt != cpuLog.end()) 534 { 535 nlohmann::json::const_iterator tsIt = metaIt->find("timestamp"); 536 if (tsIt != metaIt->end()) 537 { 538 const std::string *logTime = tsIt->get_ptr<const std::string *>(); 539 if (logTime != nullptr) 540 { 541 return *logTime; 542 } 543 } 544 } 545 BMCWEB_LOG_DEBUG << "failed to find log timestamp"; 546 547 return std::string(); 548 } 549 550 class CPULogEntry : public Node 551 { 552 public: 553 CPULogEntry(CrowApp &app) : 554 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/", 555 std::string()) 556 { 557 entityPrivileges = { 558 {boost::beast::http::verb::get, {{"Login"}}}, 559 {boost::beast::http::verb::head, {{"Login"}}}, 560 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 561 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 562 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 563 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 564 } 565 566 private: 567 void doGet(crow::Response &res, const crow::Request &req, 568 const std::vector<std::string> ¶ms) override 569 { 570 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 571 if (params.size() != 1) 572 { 573 asyncResp->res.result( 574 boost::beast::http::status::internal_server_error); 575 return; 576 } 577 const uint8_t logId = std::atoi(params[0].c_str()); 578 auto getStoredLogCallback = 579 [asyncResp, 580 logId](const boost::system::error_code ec, 581 const sdbusplus::message::variant<std::string> &resp) { 582 if (ec) 583 { 584 BMCWEB_LOG_DEBUG << "failed to get log ec: " 585 << ec.message(); 586 asyncResp->res.result( 587 boost::beast::http::status::internal_server_error); 588 return; 589 } 590 const std::string *log = 591 mapbox::getPtr<const std::string>(resp); 592 if (log == nullptr) 593 { 594 asyncResp->res.result( 595 boost::beast::http::status::internal_server_error); 596 return; 597 } 598 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 599 if (j.is_discarded()) 600 { 601 asyncResp->res.result( 602 boost::beast::http::status::internal_server_error); 603 return; 604 } 605 std::string t = getLogCreatedTime(j); 606 asyncResp->res.jsonValue = { 607 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 608 {"@odata.context", 609 "/redfish/v1/$metadata#LogEntry.LogEntry"}, 610 {"@odata.id", 611 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" + 612 std::to_string(logId)}, 613 {"Name", "CPU Debug Log"}, 614 {"Id", logId}, 615 {"EntryType", "Oem"}, 616 {"OemRecordFormat", "Intel CPU Log"}, 617 {"Oem", {{"Intel", std::move(j)}}}, 618 {"Created", std::move(t)}}; 619 }; 620 crow::connections::systemBus->async_method_call( 621 std::move(getStoredLogCallback), cpuLogObject, 622 cpuLogPath + std::string("/") + std::to_string(logId), 623 "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log"); 624 } 625 }; 626 627 class ImmediateCPULog : public Node 628 { 629 public: 630 ImmediateCPULog(CrowApp &app) : 631 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 632 "CpuLog.Immediate/") 633 { 634 entityPrivileges = { 635 {boost::beast::http::verb::get, {{"Login"}}}, 636 {boost::beast::http::verb::head, {{"Login"}}}, 637 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 638 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 639 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 640 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 641 } 642 643 private: 644 void doPost(crow::Response &res, const crow::Request &req, 645 const std::vector<std::string> ¶ms) override 646 { 647 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 648 static std::unique_ptr<sdbusplus::bus::match::match> 649 immediateLogMatcher; 650 651 // Only allow one Immediate Log request at a time 652 if (immediateLogMatcher != nullptr) 653 { 654 asyncResp->res.addHeader("Retry-After", "30"); 655 asyncResp->res.result( 656 boost::beast::http::status::service_unavailable); 657 messages::addMessageToJson( 658 asyncResp->res.jsonValue, 659 messages::serviceTemporarilyUnavailable("30"), 660 "/CpuLog.Immediate"); 661 return; 662 } 663 // Make this static so it survives outside this method 664 static boost::asio::deadline_timer timeout(*req.ioService); 665 666 timeout.expires_from_now(boost::posix_time::seconds(30)); 667 timeout.async_wait([asyncResp](const boost::system::error_code &ec) { 668 immediateLogMatcher = nullptr; 669 if (ec) 670 { 671 // operation_aborted is expected if timer is canceled before 672 // completion. 673 if (ec != boost::asio::error::operation_aborted) 674 { 675 BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 676 } 677 return; 678 } 679 BMCWEB_LOG_ERROR << "Timed out waiting for immediate log"; 680 681 asyncResp->res.result( 682 boost::beast::http::status::internal_server_error); 683 }); 684 685 auto immediateLogMatcherCallback = [asyncResp]( 686 sdbusplus::message::message &m) { 687 BMCWEB_LOG_DEBUG << "Immediate log available match fired"; 688 boost::system::error_code ec; 689 timeout.cancel(ec); 690 if (ec) 691 { 692 BMCWEB_LOG_ERROR << "error canceling timer " << ec; 693 } 694 sdbusplus::message::object_path objPath; 695 boost::container::flat_map< 696 std::string, 697 boost::container::flat_map< 698 std::string, sdbusplus::message::variant<std::string>>> 699 interfacesAdded; 700 m.read(objPath, interfacesAdded); 701 const std::string *log = mapbox::getPtr<const std::string>( 702 interfacesAdded[cpuLogInterface]["Log"]); 703 if (log == nullptr) 704 { 705 asyncResp->res.result( 706 boost::beast::http::status::internal_server_error); 707 // Careful with immediateLogMatcher. It is a unique_ptr to the 708 // match object inside which this lambda is executing. Once it 709 // is set to nullptr, the match object will be destroyed and the 710 // lambda will lose its context, including res, so it needs to 711 // be the last thing done. 712 immediateLogMatcher = nullptr; 713 return; 714 } 715 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 716 if (j.is_discarded()) 717 { 718 asyncResp->res.result( 719 boost::beast::http::status::internal_server_error); 720 // Careful with immediateLogMatcher. It is a unique_ptr to the 721 // match object inside which this lambda is executing. Once it 722 // is set to nullptr, the match object will be destroyed and the 723 // lambda will lose its context, including res, so it needs to 724 // be the last thing done. 725 immediateLogMatcher = nullptr; 726 return; 727 } 728 std::string t = getLogCreatedTime(j); 729 asyncResp->res.jsonValue = { 730 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 731 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 732 {"Name", "CPU Debug Log"}, 733 {"EntryType", "Oem"}, 734 {"OemRecordFormat", "Intel CPU Log"}, 735 {"Oem", {{"Intel", std::move(j)}}}, 736 {"Created", std::move(t)}}; 737 // Careful with immediateLogMatcher. It is a unique_ptr to the 738 // match object inside which this lambda is executing. Once it is 739 // set to nullptr, the match object will be destroyed and the lambda 740 // will lose its context, including res, so it needs to be the last 741 // thing done. 742 immediateLogMatcher = nullptr; 743 }; 744 immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>( 745 *crow::connections::systemBus, 746 sdbusplus::bus::match::rules::interfacesAdded() + 747 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath), 748 std::move(immediateLogMatcherCallback)); 749 750 auto generateImmediateLogCallback = 751 [asyncResp](const boost::system::error_code ec, 752 const std::string &resp) { 753 if (ec) 754 { 755 if (ec.value() == 756 boost::system::errc::operation_not_supported) 757 { 758 messages::addMessageToJson( 759 asyncResp->res.jsonValue, 760 messages::resourceInStandby(), "/CpuLog.Immediate"); 761 asyncResp->res.result( 762 boost::beast::http::status::service_unavailable); 763 } 764 else 765 { 766 asyncResp->res.result( 767 boost::beast::http::status::internal_server_error); 768 } 769 boost::system::error_code timeoutec; 770 timeout.cancel(timeoutec); 771 if (timeoutec) 772 { 773 BMCWEB_LOG_ERROR << "error canceling timer " 774 << timeoutec; 775 } 776 immediateLogMatcher = nullptr; 777 return; 778 } 779 }; 780 crow::connections::systemBus->async_method_call( 781 std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath, 782 cpuLogImmediateInterface, "GenerateImmediateLog"); 783 } 784 }; 785 786 class SendRawPECI : public Node 787 { 788 public: 789 SendRawPECI(CrowApp &app) : 790 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 791 "CpuLog.SendRawPeci/") 792 { 793 entityPrivileges = { 794 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 795 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 796 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 797 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 798 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 799 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 800 } 801 802 private: 803 void doPost(crow::Response &res, const crow::Request &req, 804 const std::vector<std::string> ¶ms) override 805 { 806 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 807 // Get the Raw PECI command from the request 808 nlohmann::json rawPECICmd; 809 if (!json_util::processJsonFromRequest(res, req, rawPECICmd)) 810 { 811 return; 812 } 813 // Get the Client Address from the request 814 nlohmann::json::const_iterator caIt = rawPECICmd.find("ClientAddress"); 815 if (caIt == rawPECICmd.end()) 816 { 817 messages::addMessageToJson( 818 asyncResp->res.jsonValue, 819 messages::propertyMissing("ClientAddress"), "/ClientAddress"); 820 asyncResp->res.result(boost::beast::http::status::bad_request); 821 return; 822 } 823 const uint64_t *ca = caIt->get_ptr<const uint64_t *>(); 824 if (ca == nullptr) 825 { 826 messages::addMessageToJson( 827 asyncResp->res.jsonValue, 828 messages::propertyValueTypeError(caIt->dump(), "ClientAddress"), 829 "/ClientAddress"); 830 asyncResp->res.result(boost::beast::http::status::bad_request); 831 return; 832 } 833 // Get the Read Length from the request 834 const uint8_t clientAddress = static_cast<uint8_t>(*ca); 835 nlohmann::json::const_iterator rlIt = rawPECICmd.find("ReadLength"); 836 if (rlIt == rawPECICmd.end()) 837 { 838 messages::addMessageToJson(asyncResp->res.jsonValue, 839 messages::propertyMissing("ReadLength"), 840 "/ReadLength"); 841 asyncResp->res.result(boost::beast::http::status::bad_request); 842 return; 843 } 844 const uint64_t *rl = rlIt->get_ptr<const uint64_t *>(); 845 if (rl == nullptr) 846 { 847 messages::addMessageToJson( 848 asyncResp->res.jsonValue, 849 messages::propertyValueTypeError(rlIt->dump(), "ReadLength"), 850 "/ReadLength"); 851 asyncResp->res.result(boost::beast::http::status::bad_request); 852 return; 853 } 854 // Get the PECI Command from the request 855 const uint32_t readLength = static_cast<uint32_t>(*rl); 856 nlohmann::json::const_iterator pcIt = rawPECICmd.find("PECICommand"); 857 if (pcIt == rawPECICmd.end()) 858 { 859 messages::addMessageToJson(asyncResp->res.jsonValue, 860 messages::propertyMissing("PECICommand"), 861 "/PECICommand"); 862 asyncResp->res.result(boost::beast::http::status::bad_request); 863 return; 864 } 865 std::vector<uint8_t> peciCommand; 866 for (auto pc : *pcIt) 867 { 868 const uint64_t *val = pc.get_ptr<const uint64_t *>(); 869 if (val == nullptr) 870 { 871 messages::addMessageToJson( 872 asyncResp->res.jsonValue, 873 messages::propertyValueTypeError( 874 pc.dump(), 875 "PECICommand/" + std::to_string(peciCommand.size())), 876 "/PECICommand"); 877 asyncResp->res.result(boost::beast::http::status::bad_request); 878 return; 879 } 880 peciCommand.push_back(static_cast<uint8_t>(*val)); 881 } 882 // Callback to return the Raw PECI response 883 auto sendRawPECICallback = 884 [asyncResp](const boost::system::error_code ec, 885 const std::vector<uint8_t> &resp) { 886 if (ec) 887 { 888 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: " 889 << ec.message(); 890 asyncResp->res.result( 891 boost::beast::http::status::internal_server_error); 892 return; 893 } 894 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"}, 895 {"PECIResponse", resp}}; 896 }; 897 // Call the SendRawPECI command with the provided data 898 crow::connections::systemBus->async_method_call( 899 std::move(sendRawPECICallback), cpuLogObject, cpuLogPath, 900 cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength, 901 peciCommand); 902 } 903 }; 904 905 } // namespace redfish 906