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