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