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 messages::internalError(asyncResp->res); 264 return; 265 } 266 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 267 journalTmp, sd_journal_close); 268 journalTmp = nullptr; 269 uint64_t prevTs = 0; 270 int index = 0; 271 SD_JOURNAL_FOREACH(journal.get()) 272 { 273 // Get the entry timestamp 274 uint64_t curTs = 0; 275 ret = sd_journal_get_realtime_usec(journal.get(), &curTs); 276 if (ret < 0) 277 { 278 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 279 << strerror(-ret); 280 continue; 281 } 282 // If the timestamp isn't unique, increment the index 283 if (curTs == prevTs) 284 { 285 index++; 286 } 287 else 288 { 289 // Otherwise, reset it 290 index = 0; 291 } 292 // Save the timestamp 293 prevTs = curTs; 294 295 std::string idStr(std::to_string(curTs)); 296 if (index > 0) 297 { 298 idStr += "_" + std::to_string(index); 299 } 300 logEntryArray.push_back({}); 301 nlohmann::json &bmcLogEntry = logEntryArray.back(); 302 if (fillBMCLogEntryJson(idStr, journal.get(), bmcLogEntry) != 0) 303 { 304 messages::internalError(asyncResp->res); 305 return; 306 } 307 } 308 asyncResp->res.jsonValue["Members@odata.count"] = logEntryArray.size(); 309 } 310 }; 311 312 class BMCLogEntry : public Node 313 { 314 public: 315 BMCLogEntry(CrowApp &app) : 316 Node(app, "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries/<str>/", 317 std::string()) 318 { 319 entityPrivileges = { 320 {boost::beast::http::verb::get, {{"Login"}}}, 321 {boost::beast::http::verb::head, {{"Login"}}}, 322 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 323 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 324 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 325 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 326 } 327 328 private: 329 void doGet(crow::Response &res, const crow::Request &req, 330 const std::vector<std::string> ¶ms) override 331 { 332 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 333 if (params.size() != 1) 334 { 335 messages::internalError(asyncResp->res); 336 return; 337 } 338 // Convert the unique ID back to a timestamp to find the entry 339 boost::string_view tsStr(params[0]); 340 boost::string_view indexStr(params[0]); 341 uint64_t ts = 0; 342 uint16_t index = 0; 343 auto underscorePos = tsStr.find("_"); 344 if (underscorePos == tsStr.npos) 345 { 346 // Timestamp has no index 347 ts = strtoull(tsStr.data(), nullptr, 10); 348 } 349 else 350 { 351 // Timestamp has an index 352 tsStr.remove_suffix(tsStr.size() - underscorePos + 1); 353 ts = strtoull(tsStr.data(), nullptr, 10); 354 indexStr.remove_prefix(underscorePos + 1); 355 index = 356 static_cast<uint16_t>(strtoul(indexStr.data(), nullptr, 10)); 357 } 358 359 sd_journal *journalTmp = nullptr; 360 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 361 if (ret < 0) 362 { 363 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 364 messages::internalError(asyncResp->res); 365 return; 366 } 367 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 368 journalTmp, sd_journal_close); 369 journalTmp = nullptr; 370 // Go to the timestamp in the log and move to the entry at the index 371 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 372 for (int i = 0; i <= index; i++) 373 { 374 sd_journal_next(journal.get()); 375 } 376 if (fillBMCLogEntryJson(params[0], journal.get(), 377 asyncResp->res.jsonValue) != 0) 378 { 379 messages::internalError(asyncResp->res); 380 return; 381 } 382 } 383 }; 384 385 class CPULogService : public Node 386 { 387 public: 388 template <typename CrowApp> 389 CPULogService(CrowApp &app) : 390 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/") 391 { 392 // Set the id for SubRoute 393 Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/CpuLog"; 394 entityPrivileges = { 395 {boost::beast::http::verb::get, {{"Login"}}}, 396 {boost::beast::http::verb::head, {{"Login"}}}, 397 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 398 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 399 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 400 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 401 } 402 403 private: 404 /** 405 * Functions triggers appropriate requests on DBus 406 */ 407 void doGet(crow::Response &res, const crow::Request &req, 408 const std::vector<std::string> ¶ms) override 409 { 410 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 411 // Copy over the static data to include the entries added by SubRoute 412 asyncResp->res.jsonValue = Node::json; 413 asyncResp->res.jsonValue["@odata.type"] = 414 "#LogService.v1_1_0.LogService"; 415 asyncResp->res.jsonValue["@odata.context"] = 416 "/redfish/v1/" 417 "$metadata#LogService.LogService"; 418 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service"; 419 asyncResp->res.jsonValue["Description"] = "CPU Log Service"; 420 asyncResp->res.jsonValue["Id"] = "CPU Log"; 421 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 422 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 423 asyncResp->res.jsonValue["Actions"] = { 424 {"Oem", 425 {{"#CpuLog.Immediate", 426 {{"target", 427 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 428 "CpuLog.Immediate"}}}}}}; 429 430 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI 431 asyncResp->res.jsonValue["Actions"]["Oem"].push_back( 432 {"#CpuLog.SendRawPeci", 433 {{"target", 434 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 435 "CpuLog.SendRawPeci"}}}); 436 #endif 437 } 438 }; 439 440 class CPULogEntryCollection : public Node 441 { 442 public: 443 template <typename CrowApp> 444 CPULogEntryCollection(CrowApp &app) : 445 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/") 446 { 447 // Collections use static ID for SubRoute to add to its parent, but only 448 // load dynamic data so the duplicate static members don't get displayed 449 Node::json["@odata.id"] = 450 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries"; 451 entityPrivileges = { 452 {boost::beast::http::verb::get, {{"Login"}}}, 453 {boost::beast::http::verb::head, {{"Login"}}}, 454 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 455 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 456 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 457 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 458 } 459 460 private: 461 /** 462 * Functions triggers appropriate requests on DBus 463 */ 464 void doGet(crow::Response &res, const crow::Request &req, 465 const std::vector<std::string> ¶ms) override 466 { 467 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 468 // Collections don't include the static data added by SubRoute because 469 // it has a duplicate entry for members 470 auto getLogEntriesCallback = [asyncResp]( 471 const boost::system::error_code ec, 472 const std::vector<std::string> &resp) { 473 if (ec) 474 { 475 if (ec.value() != 476 boost::system::errc::no_such_file_or_directory) 477 { 478 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 479 << ec.message(); 480 messages::internalError(asyncResp->res); 481 return; 482 } 483 } 484 asyncResp->res.jsonValue["@odata.type"] = 485 "#LogEntryCollection.LogEntryCollection"; 486 asyncResp->res.jsonValue["@odata.context"] = 487 "/redfish/v1/" 488 "$metadata#LogEntryCollection.LogEntryCollection"; 489 asyncResp->res.jsonValue["@odata.id"] = 490 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries"; 491 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries"; 492 asyncResp->res.jsonValue["Description"] = 493 "Collection of CPU Log Entries"; 494 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 495 logEntryArray = nlohmann::json::array(); 496 for (const std::string &objpath : resp) 497 { 498 // Don't list the immediate log 499 if (objpath.compare(cpuLogImmediatePath) == 0) 500 { 501 continue; 502 } 503 std::size_t lastPos = objpath.rfind("/"); 504 if (lastPos != std::string::npos) 505 { 506 logEntryArray.push_back( 507 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/" 508 "CpuLog/Entries/" + 509 objpath.substr(lastPos + 1)}}); 510 } 511 } 512 asyncResp->res.jsonValue["Members@odata.count"] = 513 logEntryArray.size(); 514 }; 515 crow::connections::systemBus->async_method_call( 516 std::move(getLogEntriesCallback), 517 "xyz.openbmc_project.ObjectMapper", 518 "/xyz/openbmc_project/object_mapper", 519 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 520 std::array<const char *, 1>{cpuLogInterface}); 521 } 522 }; 523 524 std::string getLogCreatedTime(const nlohmann::json &cpuLog) 525 { 526 nlohmann::json::const_iterator metaIt = cpuLog.find("metadata"); 527 if (metaIt != cpuLog.end()) 528 { 529 nlohmann::json::const_iterator tsIt = metaIt->find("timestamp"); 530 if (tsIt != metaIt->end()) 531 { 532 const std::string *logTime = tsIt->get_ptr<const std::string *>(); 533 if (logTime != nullptr) 534 { 535 return *logTime; 536 } 537 } 538 } 539 BMCWEB_LOG_DEBUG << "failed to find log timestamp"; 540 541 return std::string(); 542 } 543 544 class CPULogEntry : public Node 545 { 546 public: 547 CPULogEntry(CrowApp &app) : 548 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/", 549 std::string()) 550 { 551 entityPrivileges = { 552 {boost::beast::http::verb::get, {{"Login"}}}, 553 {boost::beast::http::verb::head, {{"Login"}}}, 554 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 555 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 556 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 557 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 558 } 559 560 private: 561 void doGet(crow::Response &res, const crow::Request &req, 562 const std::vector<std::string> ¶ms) override 563 { 564 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 565 if (params.size() != 1) 566 { 567 messages::internalError(asyncResp->res); 568 return; 569 } 570 const uint8_t logId = std::atoi(params[0].c_str()); 571 auto getStoredLogCallback = 572 [asyncResp, 573 logId](const boost::system::error_code ec, 574 const sdbusplus::message::variant<std::string> &resp) { 575 if (ec) 576 { 577 BMCWEB_LOG_DEBUG << "failed to get log ec: " 578 << ec.message(); 579 messages::internalError(asyncResp->res); 580 return; 581 } 582 const std::string *log = 583 mapbox::getPtr<const std::string>(resp); 584 if (log == nullptr) 585 { 586 messages::internalError(asyncResp->res); 587 return; 588 } 589 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 590 if (j.is_discarded()) 591 { 592 messages::internalError(asyncResp->res); 593 return; 594 } 595 std::string t = getLogCreatedTime(j); 596 asyncResp->res.jsonValue = { 597 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 598 {"@odata.context", 599 "/redfish/v1/$metadata#LogEntry.LogEntry"}, 600 {"@odata.id", 601 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" + 602 std::to_string(logId)}, 603 {"Name", "CPU Debug Log"}, 604 {"Id", logId}, 605 {"EntryType", "Oem"}, 606 {"OemRecordFormat", "Intel CPU Log"}, 607 {"Oem", {{"Intel", std::move(j)}}}, 608 {"Created", std::move(t)}}; 609 }; 610 crow::connections::systemBus->async_method_call( 611 std::move(getStoredLogCallback), cpuLogObject, 612 cpuLogPath + std::string("/") + std::to_string(logId), 613 "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log"); 614 } 615 }; 616 617 class ImmediateCPULog : public Node 618 { 619 public: 620 ImmediateCPULog(CrowApp &app) : 621 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 622 "CpuLog.Immediate/") 623 { 624 entityPrivileges = { 625 {boost::beast::http::verb::get, {{"Login"}}}, 626 {boost::beast::http::verb::head, {{"Login"}}}, 627 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 628 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 629 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 630 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 631 } 632 633 private: 634 void doPost(crow::Response &res, const crow::Request &req, 635 const std::vector<std::string> ¶ms) override 636 { 637 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 638 static std::unique_ptr<sdbusplus::bus::match::match> 639 immediateLogMatcher; 640 641 // Only allow one Immediate Log request at a time 642 if (immediateLogMatcher != nullptr) 643 { 644 asyncResp->res.addHeader("Retry-After", "30"); 645 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 646 return; 647 } 648 // Make this static so it survives outside this method 649 static boost::asio::deadline_timer timeout(*req.ioService); 650 651 timeout.expires_from_now(boost::posix_time::seconds(30)); 652 timeout.async_wait([asyncResp](const boost::system::error_code &ec) { 653 immediateLogMatcher = nullptr; 654 if (ec) 655 { 656 // operation_aborted is expected if timer is canceled before 657 // completion. 658 if (ec != boost::asio::error::operation_aborted) 659 { 660 BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 661 } 662 return; 663 } 664 BMCWEB_LOG_ERROR << "Timed out waiting for immediate log"; 665 666 messages::internalError(asyncResp->res); 667 }); 668 669 auto immediateLogMatcherCallback = [asyncResp]( 670 sdbusplus::message::message &m) { 671 BMCWEB_LOG_DEBUG << "Immediate log available match fired"; 672 boost::system::error_code ec; 673 timeout.cancel(ec); 674 if (ec) 675 { 676 BMCWEB_LOG_ERROR << "error canceling timer " << ec; 677 } 678 sdbusplus::message::object_path objPath; 679 boost::container::flat_map< 680 std::string, 681 boost::container::flat_map< 682 std::string, sdbusplus::message::variant<std::string>>> 683 interfacesAdded; 684 m.read(objPath, interfacesAdded); 685 const std::string *log = mapbox::getPtr<const std::string>( 686 interfacesAdded[cpuLogInterface]["Log"]); 687 if (log == nullptr) 688 { 689 messages::internalError(asyncResp->res); 690 // Careful with immediateLogMatcher. It is a unique_ptr to the 691 // match object inside which this lambda is executing. Once it 692 // is set to nullptr, the match object will be destroyed and the 693 // lambda will lose its context, including res, so it needs to 694 // be the last thing done. 695 immediateLogMatcher = nullptr; 696 return; 697 } 698 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 699 if (j.is_discarded()) 700 { 701 messages::internalError(asyncResp->res); 702 // Careful with immediateLogMatcher. It is a unique_ptr to the 703 // match object inside which this lambda is executing. Once it 704 // is set to nullptr, the match object will be destroyed and the 705 // lambda will lose its context, including res, so it needs to 706 // be the last thing done. 707 immediateLogMatcher = nullptr; 708 return; 709 } 710 std::string t = getLogCreatedTime(j); 711 asyncResp->res.jsonValue = { 712 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 713 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 714 {"Name", "CPU Debug Log"}, 715 {"EntryType", "Oem"}, 716 {"OemRecordFormat", "Intel CPU Log"}, 717 {"Oem", {{"Intel", std::move(j)}}}, 718 {"Created", std::move(t)}}; 719 // Careful with immediateLogMatcher. It is a unique_ptr to the 720 // match object inside which this lambda is executing. Once it is 721 // set to nullptr, the match object will be destroyed and the lambda 722 // will lose its context, including res, so it needs to be the last 723 // thing done. 724 immediateLogMatcher = nullptr; 725 }; 726 immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>( 727 *crow::connections::systemBus, 728 sdbusplus::bus::match::rules::interfacesAdded() + 729 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath), 730 std::move(immediateLogMatcherCallback)); 731 732 auto generateImmediateLogCallback = 733 [asyncResp](const boost::system::error_code ec, 734 const std::string &resp) { 735 if (ec) 736 { 737 if (ec.value() == 738 boost::system::errc::operation_not_supported) 739 { 740 messages::resourceInStandby(asyncResp->res); 741 } 742 else 743 { 744 messages::internalError(asyncResp->res); 745 } 746 boost::system::error_code timeoutec; 747 timeout.cancel(timeoutec); 748 if (timeoutec) 749 { 750 BMCWEB_LOG_ERROR << "error canceling timer " 751 << timeoutec; 752 } 753 immediateLogMatcher = nullptr; 754 return; 755 } 756 }; 757 crow::connections::systemBus->async_method_call( 758 std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath, 759 cpuLogImmediateInterface, "GenerateImmediateLog"); 760 } 761 }; 762 763 class SendRawPECI : public Node 764 { 765 public: 766 SendRawPECI(CrowApp &app) : 767 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 768 "CpuLog.SendRawPeci/") 769 { 770 entityPrivileges = { 771 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 772 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 773 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 774 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 775 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 776 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 777 } 778 779 private: 780 void doPost(crow::Response &res, const crow::Request &req, 781 const std::vector<std::string> ¶ms) override 782 { 783 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 784 // Get the Raw PECI command from the request 785 nlohmann::json rawPECICmd; 786 if (!json_util::processJsonFromRequest(res, req, rawPECICmd)) 787 { 788 return; 789 } 790 // Get the Client Address from the request 791 nlohmann::json::const_iterator caIt = rawPECICmd.find("ClientAddress"); 792 if (caIt == rawPECICmd.end()) 793 { 794 messages::propertyMissing(asyncResp->res, "ClientAddress", 795 "/ClientAddress"); 796 return; 797 } 798 const uint64_t *ca = caIt->get_ptr<const uint64_t *>(); 799 if (ca == nullptr) 800 { 801 messages::propertyValueTypeError(asyncResp->res, caIt->dump(), 802 "ClientAddress", "/ClientAddress"); 803 return; 804 } 805 // Get the Read Length from the request 806 const uint8_t clientAddress = static_cast<uint8_t>(*ca); 807 nlohmann::json::const_iterator rlIt = rawPECICmd.find("ReadLength"); 808 if (rlIt == rawPECICmd.end()) 809 { 810 messages::propertyMissing(asyncResp->res, "ReadLength", 811 "/ReadLength"); 812 return; 813 } 814 const uint64_t *rl = rlIt->get_ptr<const uint64_t *>(); 815 if (rl == nullptr) 816 { 817 messages::propertyValueTypeError(asyncResp->res, rlIt->dump(), 818 "ReadLength", "/ReadLength"); 819 return; 820 } 821 // Get the PECI Command from the request 822 const uint32_t readLength = static_cast<uint32_t>(*rl); 823 nlohmann::json::const_iterator pcIt = rawPECICmd.find("PECICommand"); 824 if (pcIt == rawPECICmd.end()) 825 { 826 messages::propertyMissing(asyncResp->res, "PECICommand", 827 "/PECICommand"); 828 return; 829 } 830 std::vector<uint8_t> peciCommand; 831 for (auto pc : *pcIt) 832 { 833 const uint64_t *val = pc.get_ptr<const uint64_t *>(); 834 if (val == nullptr) 835 { 836 messages::propertyValueTypeError( 837 asyncResp->res, pc.dump(), 838 "PECICommand/" + std::to_string(peciCommand.size()), 839 "/PECICommand"); 840 return; 841 } 842 peciCommand.push_back(static_cast<uint8_t>(*val)); 843 } 844 // Callback to return the Raw PECI response 845 auto sendRawPECICallback = 846 [asyncResp](const boost::system::error_code ec, 847 const std::vector<uint8_t> &resp) { 848 if (ec) 849 { 850 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: " 851 << ec.message(); 852 messages::internalError(asyncResp->res); 853 return; 854 } 855 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"}, 856 {"PECIResponse", resp}}; 857 }; 858 // Call the SendRawPECI command with the provided data 859 crow::connections::systemBus->async_method_call( 860 std::move(sendRawPECICallback), cpuLogObject, cpuLogPath, 861 cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength, 862 peciCommand); 863 } 864 }; 865 866 } // namespace redfish 867