11da66f75SEd Tanous /* 21da66f75SEd Tanous // Copyright (c) 2018 Intel Corporation 31da66f75SEd Tanous // 41da66f75SEd Tanous // Licensed under the Apache License, Version 2.0 (the "License"); 51da66f75SEd Tanous // you may not use this file except in compliance with the License. 61da66f75SEd Tanous // You may obtain a copy of the License at 71da66f75SEd Tanous // 81da66f75SEd Tanous // http://www.apache.org/licenses/LICENSE-2.0 91da66f75SEd Tanous // 101da66f75SEd Tanous // Unless required by applicable law or agreed to in writing, software 111da66f75SEd Tanous // distributed under the License is distributed on an "AS IS" BASIS, 121da66f75SEd Tanous // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 131da66f75SEd Tanous // See the License for the specific language governing permissions and 141da66f75SEd Tanous // limitations under the License. 151da66f75SEd Tanous */ 161da66f75SEd Tanous #pragma once 171da66f75SEd Tanous 181da66f75SEd Tanous #include "node.hpp" 191da66f75SEd Tanous 20e1f26343SJason M. Bills #include <systemd/sd-journal.h> 21e1f26343SJason M. Bills 221da66f75SEd Tanous #include <boost/container/flat_map.hpp> 23e1f26343SJason M. Bills #include <boost/utility/string_view.hpp> 241da66f75SEd Tanous #include <experimental/filesystem> 251da66f75SEd Tanous 261da66f75SEd Tanous namespace redfish 271da66f75SEd Tanous { 281da66f75SEd Tanous 294ed77cd5SEd Tanous constexpr char const *cpuLogObject = "com.intel.CpuDebugLog"; 304ed77cd5SEd Tanous constexpr char const *cpuLogPath = "/com/intel/CpuDebugLog"; 314ed77cd5SEd Tanous constexpr char const *cpuLogImmediatePath = "/com/intel/CpuDebugLog/Immediate"; 324ed77cd5SEd Tanous constexpr char const *cpuLogInterface = "com.intel.CpuDebugLog"; 334ed77cd5SEd Tanous constexpr char const *cpuLogImmediateInterface = 341da66f75SEd Tanous "com.intel.CpuDebugLog.Immediate"; 35e1f26343SJason M. Bills constexpr char const *cpuLogRawPECIInterface = 361da66f75SEd Tanous "com.intel.CpuDebugLog.SendRawPeci"; 371da66f75SEd Tanous 381da66f75SEd Tanous namespace fs = std::experimental::filesystem; 391da66f75SEd Tanous 4016428a1aSJason M. Bills static int getJournalMetadata(sd_journal *journal, 4116428a1aSJason M. Bills const boost::string_view &field, 4216428a1aSJason M. Bills boost::string_view &contents) 4316428a1aSJason M. Bills { 4416428a1aSJason M. Bills const char *data = nullptr; 4516428a1aSJason M. Bills size_t length = 0; 4616428a1aSJason M. Bills int ret = 0; 4716428a1aSJason M. Bills // Get the metadata from the requested field of the journal entry 4816428a1aSJason M. Bills ret = sd_journal_get_data(journal, field.data(), (const void **)&data, 4916428a1aSJason M. Bills &length); 5016428a1aSJason M. Bills if (ret < 0) 5116428a1aSJason M. Bills { 5216428a1aSJason M. Bills return ret; 5316428a1aSJason M. Bills } 5416428a1aSJason M. Bills contents = boost::string_view(data, length); 5516428a1aSJason M. Bills // Only use the content after the "=" character. 5616428a1aSJason M. Bills contents.remove_prefix(std::min(contents.find("=") + 1, contents.size())); 5716428a1aSJason M. Bills return ret; 5816428a1aSJason M. Bills } 5916428a1aSJason M. Bills 6016428a1aSJason M. Bills static int getJournalMetadata(sd_journal *journal, 6116428a1aSJason M. Bills const boost::string_view &field, const int &base, 6216428a1aSJason M. Bills int &contents) 6316428a1aSJason M. Bills { 6416428a1aSJason M. Bills int ret = 0; 6516428a1aSJason M. Bills boost::string_view metadata; 6616428a1aSJason M. Bills // Get the metadata from the requested field of the journal entry 6716428a1aSJason M. Bills ret = getJournalMetadata(journal, field, metadata); 6816428a1aSJason M. Bills if (ret < 0) 6916428a1aSJason M. Bills { 7016428a1aSJason M. Bills return ret; 7116428a1aSJason M. Bills } 7216428a1aSJason M. Bills contents = strtol(metadata.data(), nullptr, base); 7316428a1aSJason M. Bills return ret; 7416428a1aSJason M. Bills } 7516428a1aSJason M. Bills 7616428a1aSJason M. Bills static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp) 7716428a1aSJason M. Bills { 7816428a1aSJason M. Bills int ret = 0; 7916428a1aSJason M. Bills uint64_t timestamp = 0; 8016428a1aSJason M. Bills ret = sd_journal_get_realtime_usec(journal, ×tamp); 8116428a1aSJason M. Bills if (ret < 0) 8216428a1aSJason M. Bills { 8316428a1aSJason M. Bills BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 8416428a1aSJason M. Bills << strerror(-ret); 8516428a1aSJason M. Bills return false; 8616428a1aSJason M. Bills } 8716428a1aSJason M. Bills time_t t = 8816428a1aSJason M. Bills static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s 8916428a1aSJason M. Bills struct tm *loctime = localtime(&t); 9016428a1aSJason M. Bills char entryTime[64] = {}; 9116428a1aSJason M. Bills if (NULL != loctime) 9216428a1aSJason M. Bills { 9316428a1aSJason M. Bills strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime); 9416428a1aSJason M. Bills } 9516428a1aSJason M. Bills // Insert the ':' into the timezone 9616428a1aSJason M. Bills boost::string_view t1(entryTime); 9716428a1aSJason M. Bills boost::string_view t2(entryTime); 9816428a1aSJason M. Bills if (t1.size() > 2 && t2.size() > 2) 9916428a1aSJason M. Bills { 10016428a1aSJason M. Bills t1.remove_suffix(2); 10116428a1aSJason M. Bills t2.remove_prefix(t2.size() - 2); 10216428a1aSJason M. Bills } 10316428a1aSJason M. Bills entryTimestamp = t1.to_string() + ":" + t2.to_string(); 10416428a1aSJason M. Bills return true; 10516428a1aSJason M. Bills } 10616428a1aSJason M. Bills 10716428a1aSJason M. Bills static bool getSkipParam(crow::Response &res, const crow::Request &req, 10816428a1aSJason M. Bills long &skip) 10916428a1aSJason M. Bills { 11016428a1aSJason M. Bills char *skipParam = req.urlParams.get("$skip"); 11116428a1aSJason M. Bills if (skipParam != nullptr) 11216428a1aSJason M. Bills { 11316428a1aSJason M. Bills char *ptr = nullptr; 11416428a1aSJason M. Bills skip = std::strtol(skipParam, &ptr, 10); 11516428a1aSJason M. Bills if (*skipParam == '\0' || *ptr != '\0') 11616428a1aSJason M. Bills { 11716428a1aSJason M. Bills 11816428a1aSJason M. Bills messages::queryParameterValueTypeError(res, std::string(skipParam), 11916428a1aSJason M. Bills "$skip"); 12016428a1aSJason M. Bills return false; 12116428a1aSJason M. Bills } 12216428a1aSJason M. Bills if (skip < 0) 12316428a1aSJason M. Bills { 12416428a1aSJason M. Bills 12516428a1aSJason M. Bills messages::queryParameterOutOfRange(res, std::to_string(skip), 12616428a1aSJason M. Bills "$skip", "greater than 0"); 12716428a1aSJason M. Bills return false; 12816428a1aSJason M. Bills } 12916428a1aSJason M. Bills } 13016428a1aSJason M. Bills return true; 13116428a1aSJason M. Bills } 13216428a1aSJason M. Bills 13316428a1aSJason M. Bills static constexpr const long maxEntriesPerPage = 1000; 13416428a1aSJason M. Bills static bool getTopParam(crow::Response &res, const crow::Request &req, 13516428a1aSJason M. Bills long &top) 13616428a1aSJason M. Bills { 13716428a1aSJason M. Bills char *topParam = req.urlParams.get("$top"); 13816428a1aSJason M. Bills if (topParam != nullptr) 13916428a1aSJason M. Bills { 14016428a1aSJason M. Bills char *ptr = nullptr; 14116428a1aSJason M. Bills top = std::strtol(topParam, &ptr, 10); 14216428a1aSJason M. Bills if (*topParam == '\0' || *ptr != '\0') 14316428a1aSJason M. Bills { 14416428a1aSJason M. Bills messages::queryParameterValueTypeError(res, std::string(topParam), 14516428a1aSJason M. Bills "$top"); 14616428a1aSJason M. Bills return false; 14716428a1aSJason M. Bills } 14816428a1aSJason M. Bills if (top < 1 || top > maxEntriesPerPage) 14916428a1aSJason M. Bills { 15016428a1aSJason M. Bills 15116428a1aSJason M. Bills messages::queryParameterOutOfRange( 15216428a1aSJason M. Bills res, std::to_string(top), "$top", 15316428a1aSJason M. Bills "1-" + std::to_string(maxEntriesPerPage)); 15416428a1aSJason M. Bills return false; 15516428a1aSJason M. Bills } 15616428a1aSJason M. Bills } 15716428a1aSJason M. Bills return true; 15816428a1aSJason M. Bills } 15916428a1aSJason M. Bills 16016428a1aSJason M. Bills static bool getUniqueEntryID(sd_journal *journal, std::string &entryID) 16116428a1aSJason M. Bills { 16216428a1aSJason M. Bills int ret = 0; 16316428a1aSJason M. Bills static uint64_t prevTs = 0; 16416428a1aSJason M. Bills static int index = 0; 16516428a1aSJason M. Bills // Get the entry timestamp 16616428a1aSJason M. Bills uint64_t curTs = 0; 16716428a1aSJason M. Bills ret = sd_journal_get_realtime_usec(journal, &curTs); 16816428a1aSJason M. Bills if (ret < 0) 16916428a1aSJason M. Bills { 17016428a1aSJason M. Bills BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 17116428a1aSJason M. Bills << strerror(-ret); 17216428a1aSJason M. Bills return false; 17316428a1aSJason M. Bills } 17416428a1aSJason M. Bills // If the timestamp isn't unique, increment the index 17516428a1aSJason M. Bills if (curTs == prevTs) 17616428a1aSJason M. Bills { 17716428a1aSJason M. Bills index++; 17816428a1aSJason M. Bills } 17916428a1aSJason M. Bills else 18016428a1aSJason M. Bills { 18116428a1aSJason M. Bills // Otherwise, reset it 18216428a1aSJason M. Bills index = 0; 18316428a1aSJason M. Bills } 18416428a1aSJason M. Bills // Save the timestamp 18516428a1aSJason M. Bills prevTs = curTs; 18616428a1aSJason M. Bills 18716428a1aSJason M. Bills entryID = std::to_string(curTs); 18816428a1aSJason M. Bills if (index > 0) 18916428a1aSJason M. Bills { 19016428a1aSJason M. Bills entryID += "_" + std::to_string(index); 19116428a1aSJason M. Bills } 19216428a1aSJason M. Bills return true; 19316428a1aSJason M. Bills } 19416428a1aSJason M. Bills 19516428a1aSJason M. Bills static bool getTimestampFromID(crow::Response &res, const std::string &entryID, 19616428a1aSJason M. Bills uint64_t ×tamp, uint16_t &index) 19716428a1aSJason M. Bills { 19816428a1aSJason M. Bills if (entryID.empty()) 19916428a1aSJason M. Bills { 20016428a1aSJason M. Bills return false; 20116428a1aSJason M. Bills } 20216428a1aSJason M. Bills // Convert the unique ID back to a timestamp to find the entry 20316428a1aSJason M. Bills boost::string_view tsStr(entryID); 20416428a1aSJason M. Bills 20516428a1aSJason M. Bills auto underscorePos = tsStr.find("_"); 20616428a1aSJason M. Bills if (underscorePos != tsStr.npos) 20716428a1aSJason M. Bills { 20816428a1aSJason M. Bills // Timestamp has an index 20916428a1aSJason M. Bills tsStr.remove_suffix(tsStr.size() - underscorePos); 21016428a1aSJason M. Bills boost::string_view indexStr(entryID); 21116428a1aSJason M. Bills indexStr.remove_prefix(underscorePos + 1); 21216428a1aSJason M. Bills std::size_t pos; 21316428a1aSJason M. Bills try 21416428a1aSJason M. Bills { 21516428a1aSJason M. Bills index = std::stoul(indexStr.to_string(), &pos); 21616428a1aSJason M. Bills } 21716428a1aSJason M. Bills catch (std::invalid_argument) 21816428a1aSJason M. Bills { 21916428a1aSJason M. Bills messages::resourceMissingAtURI(res, entryID); 22016428a1aSJason M. Bills return false; 22116428a1aSJason M. Bills } 22216428a1aSJason M. Bills catch (std::out_of_range) 22316428a1aSJason M. Bills { 22416428a1aSJason M. Bills messages::resourceMissingAtURI(res, entryID); 22516428a1aSJason M. Bills return false; 22616428a1aSJason M. Bills } 22716428a1aSJason M. Bills if (pos != indexStr.size()) 22816428a1aSJason M. Bills { 22916428a1aSJason M. Bills messages::resourceMissingAtURI(res, entryID); 23016428a1aSJason M. Bills return false; 23116428a1aSJason M. Bills } 23216428a1aSJason M. Bills } 23316428a1aSJason M. Bills // Timestamp has no index 23416428a1aSJason M. Bills std::size_t pos; 23516428a1aSJason M. Bills try 23616428a1aSJason M. Bills { 23716428a1aSJason M. Bills timestamp = std::stoull(tsStr.to_string(), &pos); 23816428a1aSJason M. Bills } 23916428a1aSJason M. Bills catch (std::invalid_argument) 24016428a1aSJason M. Bills { 24116428a1aSJason M. Bills messages::resourceMissingAtURI(res, entryID); 24216428a1aSJason M. Bills return false; 24316428a1aSJason M. Bills } 24416428a1aSJason M. Bills catch (std::out_of_range) 24516428a1aSJason M. Bills { 24616428a1aSJason M. Bills messages::resourceMissingAtURI(res, entryID); 24716428a1aSJason M. Bills return false; 24816428a1aSJason M. Bills } 24916428a1aSJason M. Bills if (pos != tsStr.size()) 25016428a1aSJason M. Bills { 25116428a1aSJason M. Bills messages::resourceMissingAtURI(res, entryID); 25216428a1aSJason M. Bills return false; 25316428a1aSJason M. Bills } 25416428a1aSJason M. Bills return true; 25516428a1aSJason M. Bills } 25616428a1aSJason M. Bills 257*c4bf6374SJason M. Bills class SystemLogServiceCollection : public Node 2581da66f75SEd Tanous { 2591da66f75SEd Tanous public: 2601da66f75SEd Tanous template <typename CrowApp> 261*c4bf6374SJason M. Bills SystemLogServiceCollection(CrowApp &app) : 262*c4bf6374SJason M. Bills Node(app, "/redfish/v1/Systems/<str>/LogServices/", std::string()) 263*c4bf6374SJason M. Bills { 264*c4bf6374SJason M. Bills entityPrivileges = { 265*c4bf6374SJason M. Bills {boost::beast::http::verb::get, {{"Login"}}}, 266*c4bf6374SJason M. Bills {boost::beast::http::verb::head, {{"Login"}}}, 267*c4bf6374SJason M. Bills {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 268*c4bf6374SJason M. Bills {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 269*c4bf6374SJason M. Bills {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 270*c4bf6374SJason M. Bills {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 271*c4bf6374SJason M. Bills } 272*c4bf6374SJason M. Bills 273*c4bf6374SJason M. Bills private: 274*c4bf6374SJason M. Bills /** 275*c4bf6374SJason M. Bills * Functions triggers appropriate requests on DBus 276*c4bf6374SJason M. Bills */ 277*c4bf6374SJason M. Bills void doGet(crow::Response &res, const crow::Request &req, 278*c4bf6374SJason M. Bills const std::vector<std::string> ¶ms) override 279*c4bf6374SJason M. Bills { 280*c4bf6374SJason M. Bills std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 281*c4bf6374SJason M. Bills const std::string &name = params[0]; 282*c4bf6374SJason M. Bills // Collections don't include the static data added by SubRoute because 283*c4bf6374SJason M. Bills // it has a duplicate entry for members 284*c4bf6374SJason M. Bills asyncResp->res.jsonValue["@odata.type"] = 285*c4bf6374SJason M. Bills "#LogServiceCollection.LogServiceCollection"; 286*c4bf6374SJason M. Bills asyncResp->res.jsonValue["@odata.context"] = 287*c4bf6374SJason M. Bills "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; 288*c4bf6374SJason M. Bills asyncResp->res.jsonValue["@odata.id"] = 289*c4bf6374SJason M. Bills "/redfish/v1/Systems/" + name + "/LogServices"; 290*c4bf6374SJason M. Bills asyncResp->res.jsonValue["Name"] = "System Log Services Collection"; 291*c4bf6374SJason M. Bills asyncResp->res.jsonValue["Description"] = 292*c4bf6374SJason M. Bills "Collection of LogServices for this Computer System"; 293*c4bf6374SJason M. Bills nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 294*c4bf6374SJason M. Bills logServiceArray = nlohmann::json::array(); 295*c4bf6374SJason M. Bills logServiceArray.push_back({{"@odata.id", "/redfish/v1/Systems/" + name + 296*c4bf6374SJason M. Bills "/LogServices/EventLog"}}); 297*c4bf6374SJason M. Bills asyncResp->res.jsonValue["Members@odata.count"] = 298*c4bf6374SJason M. Bills logServiceArray.size(); 299*c4bf6374SJason M. Bills } 300*c4bf6374SJason M. Bills }; 301*c4bf6374SJason M. Bills 302*c4bf6374SJason M. Bills class EventLogService : public Node 303*c4bf6374SJason M. Bills { 304*c4bf6374SJason M. Bills public: 305*c4bf6374SJason M. Bills template <typename CrowApp> 306*c4bf6374SJason M. Bills EventLogService(CrowApp &app) : 307*c4bf6374SJason M. Bills Node(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/", 308*c4bf6374SJason M. Bills std::string()) 309*c4bf6374SJason M. Bills { 310*c4bf6374SJason M. Bills entityPrivileges = { 311*c4bf6374SJason M. Bills {boost::beast::http::verb::get, {{"Login"}}}, 312*c4bf6374SJason M. Bills {boost::beast::http::verb::head, {{"Login"}}}, 313*c4bf6374SJason M. Bills {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 314*c4bf6374SJason M. Bills {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 315*c4bf6374SJason M. Bills {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 316*c4bf6374SJason M. Bills {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 317*c4bf6374SJason M. Bills } 318*c4bf6374SJason M. Bills 319*c4bf6374SJason M. Bills private: 320*c4bf6374SJason M. Bills void doGet(crow::Response &res, const crow::Request &req, 321*c4bf6374SJason M. Bills const std::vector<std::string> ¶ms) override 322*c4bf6374SJason M. Bills { 323*c4bf6374SJason M. Bills std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 324*c4bf6374SJason M. Bills 325*c4bf6374SJason M. Bills const std::string &name = params[0]; 326*c4bf6374SJason M. Bills asyncResp->res.jsonValue["@odata.id"] = 327*c4bf6374SJason M. Bills "/redfish/v1/Systems/" + name + "/LogServices/EventLog"; 328*c4bf6374SJason M. Bills asyncResp->res.jsonValue["@odata.type"] = 329*c4bf6374SJason M. Bills "#LogService.v1_1_0.LogService"; 330*c4bf6374SJason M. Bills asyncResp->res.jsonValue["@odata.context"] = 331*c4bf6374SJason M. Bills "/redfish/v1/$metadata#LogService.LogService"; 332*c4bf6374SJason M. Bills asyncResp->res.jsonValue["Name"] = "Event Log Service"; 333*c4bf6374SJason M. Bills asyncResp->res.jsonValue["Description"] = "System Event Log Service"; 334*c4bf6374SJason M. Bills asyncResp->res.jsonValue["Id"] = "Event Log"; 335*c4bf6374SJason M. Bills asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 336*c4bf6374SJason M. Bills asyncResp->res.jsonValue["Entries"] = { 337*c4bf6374SJason M. Bills {"@odata.id", 338*c4bf6374SJason M. Bills "/redfish/v1/Systems/" + name + "/LogServices/EventLog/Entries"}}; 339*c4bf6374SJason M. Bills } 340*c4bf6374SJason M. Bills }; 341*c4bf6374SJason M. Bills 342*c4bf6374SJason M. Bills static int fillEventLogEntryJson(const std::string &systemName, 343*c4bf6374SJason M. Bills const std::string &bmcLogEntryID, 344*c4bf6374SJason M. Bills const boost::string_view &messageID, 345*c4bf6374SJason M. Bills sd_journal *journal, 346*c4bf6374SJason M. Bills nlohmann::json &bmcLogEntryJson) 347*c4bf6374SJason M. Bills { 348*c4bf6374SJason M. Bills // Get the Log Entry contents 349*c4bf6374SJason M. Bills int ret = 0; 350*c4bf6374SJason M. Bills 351*c4bf6374SJason M. Bills boost::string_view msg; 352*c4bf6374SJason M. Bills ret = getJournalMetadata(journal, "MESSAGE", msg); 353*c4bf6374SJason M. Bills if (ret < 0) 354*c4bf6374SJason M. Bills { 355*c4bf6374SJason M. Bills BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 356*c4bf6374SJason M. Bills return 1; 357*c4bf6374SJason M. Bills } 358*c4bf6374SJason M. Bills 359*c4bf6374SJason M. Bills // Get the severity from the PRIORITY field 360*c4bf6374SJason M. Bills int severity = 8; // Default to an invalid priority 361*c4bf6374SJason M. Bills ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 362*c4bf6374SJason M. Bills if (ret < 0) 363*c4bf6374SJason M. Bills { 364*c4bf6374SJason M. Bills BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 365*c4bf6374SJason M. Bills return 1; 366*c4bf6374SJason M. Bills } 367*c4bf6374SJason M. Bills 368*c4bf6374SJason M. Bills // Get the MessageArgs from the journal entry by finding all of the 369*c4bf6374SJason M. Bills // REDFISH_MESSAGE_ARG_x fields 370*c4bf6374SJason M. Bills const void *data; 371*c4bf6374SJason M. Bills size_t length; 372*c4bf6374SJason M. Bills std::vector<std::string> messageArgs; 373*c4bf6374SJason M. Bills SD_JOURNAL_FOREACH_DATA(journal, data, length) 374*c4bf6374SJason M. Bills { 375*c4bf6374SJason M. Bills boost::string_view field(static_cast<const char *>(data), length); 376*c4bf6374SJason M. Bills if (field.starts_with("REDFISH_MESSAGE_ARG_")) 377*c4bf6374SJason M. Bills { 378*c4bf6374SJason M. Bills // Get the Arg number from the field name 379*c4bf6374SJason M. Bills field.remove_prefix(sizeof("REDFISH_MESSAGE_ARG_") - 1); 380*c4bf6374SJason M. Bills if (field.empty()) 381*c4bf6374SJason M. Bills { 382*c4bf6374SJason M. Bills continue; 383*c4bf6374SJason M. Bills } 384*c4bf6374SJason M. Bills int argNum = std::strtoul(field.data(), nullptr, 10); 385*c4bf6374SJason M. Bills if (argNum == 0) 386*c4bf6374SJason M. Bills { 387*c4bf6374SJason M. Bills continue; 388*c4bf6374SJason M. Bills } 389*c4bf6374SJason M. Bills // Get the Arg value after the "=" character. 390*c4bf6374SJason M. Bills field.remove_prefix(std::min(field.find("=") + 1, field.size())); 391*c4bf6374SJason M. Bills // Make sure we have enough space in messageArgs 392*c4bf6374SJason M. Bills if (argNum > messageArgs.size()) 393*c4bf6374SJason M. Bills { 394*c4bf6374SJason M. Bills messageArgs.resize(argNum); 395*c4bf6374SJason M. Bills } 396*c4bf6374SJason M. Bills messageArgs[argNum - 1] = field.to_string(); 397*c4bf6374SJason M. Bills } 398*c4bf6374SJason M. Bills } 399*c4bf6374SJason M. Bills 400*c4bf6374SJason M. Bills // Get the Created time from the timestamp 401*c4bf6374SJason M. Bills std::string entryTimeStr; 402*c4bf6374SJason M. Bills if (!getEntryTimestamp(journal, entryTimeStr)) 403*c4bf6374SJason M. Bills { 404*c4bf6374SJason M. Bills return 1; 405*c4bf6374SJason M. Bills } 406*c4bf6374SJason M. Bills 407*c4bf6374SJason M. Bills // Fill in the log entry with the gathered data 408*c4bf6374SJason M. Bills bmcLogEntryJson = { 409*c4bf6374SJason M. Bills {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 410*c4bf6374SJason M. Bills {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 411*c4bf6374SJason M. Bills {"@odata.id", "/redfish/v1/Systems/" + systemName + 412*c4bf6374SJason M. Bills "/LogServices/EventLog/Entries/" + bmcLogEntryID}, 413*c4bf6374SJason M. Bills {"Name", "System Event Log Entry"}, 414*c4bf6374SJason M. Bills {"Id", bmcLogEntryID}, 415*c4bf6374SJason M. Bills {"Message", msg}, 416*c4bf6374SJason M. Bills {"MessageId", messageID}, 417*c4bf6374SJason M. Bills {"MessageArgs", std::move(messageArgs)}, 418*c4bf6374SJason M. Bills {"EntryType", "Event"}, 419*c4bf6374SJason M. Bills {"Severity", 420*c4bf6374SJason M. Bills severity <= 2 ? "Critical" 421*c4bf6374SJason M. Bills : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""}, 422*c4bf6374SJason M. Bills {"Created", std::move(entryTimeStr)}}; 423*c4bf6374SJason M. Bills return 0; 424*c4bf6374SJason M. Bills } 425*c4bf6374SJason M. Bills 426*c4bf6374SJason M. Bills class EventLogEntryCollection : public Node 427*c4bf6374SJason M. Bills { 428*c4bf6374SJason M. Bills public: 429*c4bf6374SJason M. Bills template <typename CrowApp> 430*c4bf6374SJason M. Bills EventLogEntryCollection(CrowApp &app) : 431*c4bf6374SJason M. Bills Node(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/", 432*c4bf6374SJason M. Bills std::string()) 433*c4bf6374SJason M. Bills { 434*c4bf6374SJason M. Bills entityPrivileges = { 435*c4bf6374SJason M. Bills {boost::beast::http::verb::get, {{"Login"}}}, 436*c4bf6374SJason M. Bills {boost::beast::http::verb::head, {{"Login"}}}, 437*c4bf6374SJason M. Bills {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 438*c4bf6374SJason M. Bills {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 439*c4bf6374SJason M. Bills {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 440*c4bf6374SJason M. Bills {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 441*c4bf6374SJason M. Bills } 442*c4bf6374SJason M. Bills 443*c4bf6374SJason M. Bills private: 444*c4bf6374SJason M. Bills void doGet(crow::Response &res, const crow::Request &req, 445*c4bf6374SJason M. Bills const std::vector<std::string> ¶ms) override 446*c4bf6374SJason M. Bills { 447*c4bf6374SJason M. Bills std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 448*c4bf6374SJason M. Bills long skip = 0; 449*c4bf6374SJason M. Bills long top = maxEntriesPerPage; // Show max entries by default 450*c4bf6374SJason M. Bills if (!getSkipParam(asyncResp->res, req, skip)) 451*c4bf6374SJason M. Bills { 452*c4bf6374SJason M. Bills return; 453*c4bf6374SJason M. Bills } 454*c4bf6374SJason M. Bills if (!getTopParam(asyncResp->res, req, top)) 455*c4bf6374SJason M. Bills { 456*c4bf6374SJason M. Bills return; 457*c4bf6374SJason M. Bills } 458*c4bf6374SJason M. Bills const std::string &name = params[0]; 459*c4bf6374SJason M. Bills // Collections don't include the static data added by SubRoute because 460*c4bf6374SJason M. Bills // it has a duplicate entry for members 461*c4bf6374SJason M. Bills asyncResp->res.jsonValue["@odata.type"] = 462*c4bf6374SJason M. Bills "#LogEntryCollection.LogEntryCollection"; 463*c4bf6374SJason M. Bills asyncResp->res.jsonValue["@odata.context"] = 464*c4bf6374SJason M. Bills "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 465*c4bf6374SJason M. Bills asyncResp->res.jsonValue["@odata.id"] = 466*c4bf6374SJason M. Bills "/redfish/v1/Systems/" + name + "/LogServices/EventLog/Entries"; 467*c4bf6374SJason M. Bills asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 468*c4bf6374SJason M. Bills asyncResp->res.jsonValue["Description"] = 469*c4bf6374SJason M. Bills "Collection of System Event Log Entries"; 470*c4bf6374SJason M. Bills nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 471*c4bf6374SJason M. Bills logEntryArray = nlohmann::json::array(); 472*c4bf6374SJason M. Bills 473*c4bf6374SJason M. Bills // Go through the journal and create a unique ID for each entry 474*c4bf6374SJason M. Bills sd_journal *journalTmp = nullptr; 475*c4bf6374SJason M. Bills int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 476*c4bf6374SJason M. Bills if (ret < 0) 477*c4bf6374SJason M. Bills { 478*c4bf6374SJason M. Bills BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 479*c4bf6374SJason M. Bills messages::internalError(asyncResp->res); 480*c4bf6374SJason M. Bills return; 481*c4bf6374SJason M. Bills } 482*c4bf6374SJason M. Bills std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 483*c4bf6374SJason M. Bills journalTmp, sd_journal_close); 484*c4bf6374SJason M. Bills journalTmp = nullptr; 485*c4bf6374SJason M. Bills uint64_t entryCount = 0; 486*c4bf6374SJason M. Bills SD_JOURNAL_FOREACH(journal.get()) 487*c4bf6374SJason M. Bills { 488*c4bf6374SJason M. Bills // Look for only journal entries that contain a REDFISH_MESSAGE_ID 489*c4bf6374SJason M. Bills // field 490*c4bf6374SJason M. Bills boost::string_view messageID; 491*c4bf6374SJason M. Bills ret = getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", 492*c4bf6374SJason M. Bills messageID); 493*c4bf6374SJason M. Bills if (ret < 0) 494*c4bf6374SJason M. Bills { 495*c4bf6374SJason M. Bills continue; 496*c4bf6374SJason M. Bills } 497*c4bf6374SJason M. Bills 498*c4bf6374SJason M. Bills entryCount++; 499*c4bf6374SJason M. Bills // Handle paging using skip (number of entries to skip from the 500*c4bf6374SJason M. Bills // start) and top (number of entries to display) 501*c4bf6374SJason M. Bills if (entryCount <= skip || entryCount > skip + top) 502*c4bf6374SJason M. Bills { 503*c4bf6374SJason M. Bills continue; 504*c4bf6374SJason M. Bills } 505*c4bf6374SJason M. Bills 506*c4bf6374SJason M. Bills std::string idStr; 507*c4bf6374SJason M. Bills if (!getUniqueEntryID(journal.get(), idStr)) 508*c4bf6374SJason M. Bills { 509*c4bf6374SJason M. Bills continue; 510*c4bf6374SJason M. Bills } 511*c4bf6374SJason M. Bills 512*c4bf6374SJason M. Bills logEntryArray.push_back({}); 513*c4bf6374SJason M. Bills nlohmann::json &bmcLogEntry = logEntryArray.back(); 514*c4bf6374SJason M. Bills if (fillEventLogEntryJson(name, idStr, messageID, journal.get(), 515*c4bf6374SJason M. Bills bmcLogEntry) != 0) 516*c4bf6374SJason M. Bills { 517*c4bf6374SJason M. Bills messages::internalError(asyncResp->res); 518*c4bf6374SJason M. Bills return; 519*c4bf6374SJason M. Bills } 520*c4bf6374SJason M. Bills } 521*c4bf6374SJason M. Bills asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 522*c4bf6374SJason M. Bills if (skip + top < entryCount) 523*c4bf6374SJason M. Bills { 524*c4bf6374SJason M. Bills asyncResp->res.jsonValue["Members@odata.nextLink"] = 525*c4bf6374SJason M. Bills "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries?$skip=" + 526*c4bf6374SJason M. Bills std::to_string(skip + top); 527*c4bf6374SJason M. Bills } 528*c4bf6374SJason M. Bills } 529*c4bf6374SJason M. Bills }; 530*c4bf6374SJason M. Bills 531*c4bf6374SJason M. Bills class EventLogEntry : public Node 532*c4bf6374SJason M. Bills { 533*c4bf6374SJason M. Bills public: 534*c4bf6374SJason M. Bills EventLogEntry(CrowApp &app) : 535*c4bf6374SJason M. Bills Node(app, 536*c4bf6374SJason M. Bills "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/", 537*c4bf6374SJason M. Bills std::string(), std::string()) 538*c4bf6374SJason M. Bills { 539*c4bf6374SJason M. Bills entityPrivileges = { 540*c4bf6374SJason M. Bills {boost::beast::http::verb::get, {{"Login"}}}, 541*c4bf6374SJason M. Bills {boost::beast::http::verb::head, {{"Login"}}}, 542*c4bf6374SJason M. Bills {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 543*c4bf6374SJason M. Bills {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 544*c4bf6374SJason M. Bills {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 545*c4bf6374SJason M. Bills {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 546*c4bf6374SJason M. Bills } 547*c4bf6374SJason M. Bills 548*c4bf6374SJason M. Bills private: 549*c4bf6374SJason M. Bills void doGet(crow::Response &res, const crow::Request &req, 550*c4bf6374SJason M. Bills const std::vector<std::string> ¶ms) override 551*c4bf6374SJason M. Bills { 552*c4bf6374SJason M. Bills std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 553*c4bf6374SJason M. Bills if (params.size() != 2) 554*c4bf6374SJason M. Bills { 555*c4bf6374SJason M. Bills messages::internalError(asyncResp->res); 556*c4bf6374SJason M. Bills return; 557*c4bf6374SJason M. Bills } 558*c4bf6374SJason M. Bills const std::string &name = params[0]; 559*c4bf6374SJason M. Bills const std::string &entryID = params[1]; 560*c4bf6374SJason M. Bills // Convert the unique ID back to a timestamp to find the entry 561*c4bf6374SJason M. Bills uint64_t ts = 0; 562*c4bf6374SJason M. Bills uint16_t index = 0; 563*c4bf6374SJason M. Bills if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 564*c4bf6374SJason M. Bills { 565*c4bf6374SJason M. Bills return; 566*c4bf6374SJason M. Bills } 567*c4bf6374SJason M. Bills 568*c4bf6374SJason M. Bills sd_journal *journalTmp = nullptr; 569*c4bf6374SJason M. Bills int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 570*c4bf6374SJason M. Bills if (ret < 0) 571*c4bf6374SJason M. Bills { 572*c4bf6374SJason M. Bills BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 573*c4bf6374SJason M. Bills messages::internalError(asyncResp->res); 574*c4bf6374SJason M. Bills return; 575*c4bf6374SJason M. Bills } 576*c4bf6374SJason M. Bills std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 577*c4bf6374SJason M. Bills journalTmp, sd_journal_close); 578*c4bf6374SJason M. Bills journalTmp = nullptr; 579*c4bf6374SJason M. Bills // Go to the timestamp in the log and move to the entry at the index 580*c4bf6374SJason M. Bills ret = sd_journal_seek_realtime_usec(journal.get(), ts); 581*c4bf6374SJason M. Bills for (int i = 0; i <= index; i++) 582*c4bf6374SJason M. Bills { 583*c4bf6374SJason M. Bills sd_journal_next(journal.get()); 584*c4bf6374SJason M. Bills } 585*c4bf6374SJason M. Bills // Confirm that the entry ID matches what was requested 586*c4bf6374SJason M. Bills std::string idStr; 587*c4bf6374SJason M. Bills if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID) 588*c4bf6374SJason M. Bills { 589*c4bf6374SJason M. Bills messages::resourceMissingAtURI(asyncResp->res, entryID); 590*c4bf6374SJason M. Bills return; 591*c4bf6374SJason M. Bills } 592*c4bf6374SJason M. Bills 593*c4bf6374SJason M. Bills // only use journal entries that contain a REDFISH_MESSAGE_ID 594*c4bf6374SJason M. Bills // field 595*c4bf6374SJason M. Bills boost::string_view messageID; 596*c4bf6374SJason M. Bills ret = 597*c4bf6374SJason M. Bills getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", messageID); 598*c4bf6374SJason M. Bills if (ret < 0) 599*c4bf6374SJason M. Bills { 600*c4bf6374SJason M. Bills messages::resourceNotFound(asyncResp->res, "LogEntry", name); 601*c4bf6374SJason M. Bills return; 602*c4bf6374SJason M. Bills } 603*c4bf6374SJason M. Bills 604*c4bf6374SJason M. Bills if (fillEventLogEntryJson(name, entryID, messageID, journal.get(), 605*c4bf6374SJason M. Bills asyncResp->res.jsonValue) != 0) 606*c4bf6374SJason M. Bills { 607*c4bf6374SJason M. Bills messages::internalError(asyncResp->res); 608*c4bf6374SJason M. Bills return; 609*c4bf6374SJason M. Bills } 610*c4bf6374SJason M. Bills } 611*c4bf6374SJason M. Bills }; 612*c4bf6374SJason M. Bills 613*c4bf6374SJason M. Bills class BMCLogServiceCollection : public Node 614*c4bf6374SJason M. Bills { 615*c4bf6374SJason M. Bills public: 616*c4bf6374SJason M. Bills template <typename CrowApp> 617*c4bf6374SJason M. Bills BMCLogServiceCollection(CrowApp &app) : 6184ed77cd5SEd Tanous Node(app, "/redfish/v1/Managers/bmc/LogServices/") 6191da66f75SEd Tanous { 6201da66f75SEd Tanous // Collections use static ID for SubRoute to add to its parent, but only 6211da66f75SEd Tanous // load dynamic data so the duplicate static members don't get displayed 6224ed77cd5SEd Tanous Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices"; 6231da66f75SEd Tanous entityPrivileges = { 624e1f26343SJason M. Bills {boost::beast::http::verb::get, {{"Login"}}}, 625e1f26343SJason M. Bills {boost::beast::http::verb::head, {{"Login"}}}, 626e1f26343SJason M. Bills {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 627e1f26343SJason M. Bills {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 628e1f26343SJason M. Bills {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 629e1f26343SJason M. Bills {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 6301da66f75SEd Tanous } 6311da66f75SEd Tanous 6321da66f75SEd Tanous private: 6331da66f75SEd Tanous /** 6341da66f75SEd Tanous * Functions triggers appropriate requests on DBus 6351da66f75SEd Tanous */ 6361da66f75SEd Tanous void doGet(crow::Response &res, const crow::Request &req, 6371da66f75SEd Tanous const std::vector<std::string> ¶ms) override 6381da66f75SEd Tanous { 639e1f26343SJason M. Bills std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 6401da66f75SEd Tanous // Collections don't include the static data added by SubRoute because 6411da66f75SEd Tanous // it has a duplicate entry for members 642e1f26343SJason M. Bills asyncResp->res.jsonValue["@odata.type"] = 6431da66f75SEd Tanous "#LogServiceCollection.LogServiceCollection"; 644e1f26343SJason M. Bills asyncResp->res.jsonValue["@odata.context"] = 645*c4bf6374SJason M. Bills "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; 646e1f26343SJason M. Bills asyncResp->res.jsonValue["@odata.id"] = 647e1f26343SJason M. Bills "/redfish/v1/Managers/bmc/LogServices"; 648e1f26343SJason M. Bills asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; 649e1f26343SJason M. Bills asyncResp->res.jsonValue["Description"] = 6501da66f75SEd Tanous "Collection of LogServices for this Manager"; 651*c4bf6374SJason M. Bills nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 652*c4bf6374SJason M. Bills logServiceArray = nlohmann::json::array(); 653*c4bf6374SJason M. Bills #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 654*c4bf6374SJason M. Bills logServiceArray.push_back( 655*c4bf6374SJason M. Bills {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}}); 656*c4bf6374SJason M. Bills #endif 6571da66f75SEd Tanous #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG 658*c4bf6374SJason M. Bills logServiceArray.push_back( 6594ed77cd5SEd Tanous {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/CpuLog"}}); 6601da66f75SEd Tanous #endif 661e1f26343SJason M. Bills asyncResp->res.jsonValue["Members@odata.count"] = 662*c4bf6374SJason M. Bills logServiceArray.size(); 6631da66f75SEd Tanous } 6641da66f75SEd Tanous }; 6651da66f75SEd Tanous 666*c4bf6374SJason M. Bills class BMCJournalLogService : public Node 6671da66f75SEd Tanous { 6681da66f75SEd Tanous public: 6691da66f75SEd Tanous template <typename CrowApp> 670*c4bf6374SJason M. Bills BMCJournalLogService(CrowApp &app) : 671*c4bf6374SJason M. Bills Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 672e1f26343SJason M. Bills { 673e1f26343SJason M. Bills // Set the id for SubRoute 674*c4bf6374SJason M. Bills Node::json["@odata.id"] = 675*c4bf6374SJason M. Bills "/redfish/v1/Managers/bmc/LogServices/Journal"; 676e1f26343SJason M. Bills entityPrivileges = { 677e1f26343SJason M. Bills {boost::beast::http::verb::get, {{"Login"}}}, 678e1f26343SJason M. Bills {boost::beast::http::verb::head, {{"Login"}}}, 679e1f26343SJason M. Bills {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 680e1f26343SJason M. Bills {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 681e1f26343SJason M. Bills {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 682e1f26343SJason M. Bills {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 683e1f26343SJason M. Bills } 684e1f26343SJason M. Bills 685e1f26343SJason M. Bills private: 686e1f26343SJason M. Bills void doGet(crow::Response &res, const crow::Request &req, 687e1f26343SJason M. Bills const std::vector<std::string> ¶ms) override 688e1f26343SJason M. Bills { 689e1f26343SJason M. Bills std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 690e1f26343SJason M. Bills // Copy over the static data to include the entries added by SubRoute 691e1f26343SJason M. Bills asyncResp->res.jsonValue = Node::json; 692e1f26343SJason M. Bills asyncResp->res.jsonValue["@odata.type"] = 693e1f26343SJason M. Bills "#LogService.v1_1_0.LogService"; 694e1f26343SJason M. Bills asyncResp->res.jsonValue["@odata.context"] = 695e1f26343SJason M. Bills "/redfish/v1/$metadata#LogService.LogService"; 696*c4bf6374SJason M. Bills asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 697*c4bf6374SJason M. Bills asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 698*c4bf6374SJason M. Bills asyncResp->res.jsonValue["Id"] = "BMC Journal"; 699e1f26343SJason M. Bills asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 700e1f26343SJason M. Bills } 701e1f26343SJason M. Bills }; 702e1f26343SJason M. Bills 703*c4bf6374SJason M. Bills static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID, 704e1f26343SJason M. Bills sd_journal *journal, 705*c4bf6374SJason M. Bills nlohmann::json &bmcJournalLogEntryJson) 706e1f26343SJason M. Bills { 707e1f26343SJason M. Bills // Get the Log Entry contents 708e1f26343SJason M. Bills int ret = 0; 709e1f26343SJason M. Bills 71016428a1aSJason M. Bills boost::string_view msg; 71116428a1aSJason M. Bills ret = getJournalMetadata(journal, "MESSAGE", msg); 712e1f26343SJason M. Bills if (ret < 0) 713e1f26343SJason M. Bills { 714e1f26343SJason M. Bills BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 715e1f26343SJason M. Bills return 1; 716e1f26343SJason M. Bills } 717e1f26343SJason M. Bills 718e1f26343SJason M. Bills // Get the severity from the PRIORITY field 719e1f26343SJason M. Bills int severity = 8; // Default to an invalid priority 72016428a1aSJason M. Bills ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 721e1f26343SJason M. Bills if (ret < 0) 722e1f26343SJason M. Bills { 723e1f26343SJason M. Bills BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 724e1f26343SJason M. Bills return 1; 725e1f26343SJason M. Bills } 726e1f26343SJason M. Bills 727e1f26343SJason M. Bills // Get the Created time from the timestamp 72816428a1aSJason M. Bills std::string entryTimeStr; 72916428a1aSJason M. Bills if (!getEntryTimestamp(journal, entryTimeStr)) 730e1f26343SJason M. Bills { 73116428a1aSJason M. Bills return 1; 732e1f26343SJason M. Bills } 733e1f26343SJason M. Bills 734e1f26343SJason M. Bills // Fill in the log entry with the gathered data 735*c4bf6374SJason M. Bills bmcJournalLogEntryJson = { 736e1f26343SJason M. Bills {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 737e1f26343SJason M. Bills {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 738*c4bf6374SJason M. Bills {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + 739*c4bf6374SJason M. Bills bmcJournalLogEntryID}, 740e1f26343SJason M. Bills {"Name", "BMC Journal Entry"}, 741*c4bf6374SJason M. Bills {"Id", bmcJournalLogEntryID}, 74216428a1aSJason M. Bills {"Message", msg}, 743e1f26343SJason M. Bills {"EntryType", "Oem"}, 744e1f26343SJason M. Bills {"Severity", 745e1f26343SJason M. Bills severity <= 2 ? "Critical" 746e1f26343SJason M. Bills : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""}, 747e1f26343SJason M. Bills {"OemRecordFormat", "Intel BMC Journal Entry"}, 748e1f26343SJason M. Bills {"Created", std::move(entryTimeStr)}}; 749e1f26343SJason M. Bills return 0; 750e1f26343SJason M. Bills } 751e1f26343SJason M. Bills 752*c4bf6374SJason M. Bills class BMCJournalLogEntryCollection : public Node 753e1f26343SJason M. Bills { 754e1f26343SJason M. Bills public: 755e1f26343SJason M. Bills template <typename CrowApp> 756*c4bf6374SJason M. Bills BMCJournalLogEntryCollection(CrowApp &app) : 757*c4bf6374SJason M. Bills Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 758e1f26343SJason M. Bills { 759e1f26343SJason M. Bills // Collections use static ID for SubRoute to add to its parent, but only 760e1f26343SJason M. Bills // load dynamic data so the duplicate static members don't get displayed 761e1f26343SJason M. Bills Node::json["@odata.id"] = 762*c4bf6374SJason M. Bills "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 763e1f26343SJason M. Bills entityPrivileges = { 764e1f26343SJason M. Bills {boost::beast::http::verb::get, {{"Login"}}}, 765e1f26343SJason M. Bills {boost::beast::http::verb::head, {{"Login"}}}, 766e1f26343SJason M. Bills {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 767e1f26343SJason M. Bills {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 768e1f26343SJason M. Bills {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 769e1f26343SJason M. Bills {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 770e1f26343SJason M. Bills } 771e1f26343SJason M. Bills 772e1f26343SJason M. Bills private: 773e1f26343SJason M. Bills void doGet(crow::Response &res, const crow::Request &req, 774e1f26343SJason M. Bills const std::vector<std::string> ¶ms) override 775e1f26343SJason M. Bills { 776e1f26343SJason M. Bills std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 777193ad2faSJason M. Bills static constexpr const long maxEntriesPerPage = 1000; 778193ad2faSJason M. Bills long skip = 0; 779193ad2faSJason M. Bills long top = maxEntriesPerPage; // Show max entries by default 78016428a1aSJason M. Bills if (!getSkipParam(asyncResp->res, req, skip)) 781193ad2faSJason M. Bills { 782193ad2faSJason M. Bills return; 783193ad2faSJason M. Bills } 78416428a1aSJason M. Bills if (!getTopParam(asyncResp->res, req, top)) 785193ad2faSJason M. Bills { 786193ad2faSJason M. Bills return; 787193ad2faSJason M. Bills } 788e1f26343SJason M. Bills // Collections don't include the static data added by SubRoute because 789e1f26343SJason M. Bills // it has a duplicate entry for members 790e1f26343SJason M. Bills asyncResp->res.jsonValue["@odata.type"] = 791e1f26343SJason M. Bills "#LogEntryCollection.LogEntryCollection"; 792e1f26343SJason M. Bills asyncResp->res.jsonValue["@odata.context"] = 793*c4bf6374SJason M. Bills "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 794e1f26343SJason M. Bills asyncResp->res.jsonValue["@odata.id"] = 795*c4bf6374SJason M. Bills "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 796e1f26343SJason M. Bills asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 797e1f26343SJason M. Bills asyncResp->res.jsonValue["Description"] = 798e1f26343SJason M. Bills "Collection of BMC Journal Entries"; 799e1f26343SJason M. Bills nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 800e1f26343SJason M. Bills logEntryArray = nlohmann::json::array(); 801e1f26343SJason M. Bills 802e1f26343SJason M. Bills // Go through the journal and use the timestamp to create a unique ID 803e1f26343SJason M. Bills // for each entry 804e1f26343SJason M. Bills sd_journal *journalTmp = nullptr; 805e1f26343SJason M. Bills int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 806e1f26343SJason M. Bills if (ret < 0) 807e1f26343SJason M. Bills { 808e1f26343SJason M. Bills BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 809f12894f8SJason M. Bills messages::internalError(asyncResp->res); 810e1f26343SJason M. Bills return; 811e1f26343SJason M. Bills } 812e1f26343SJason M. Bills std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 813e1f26343SJason M. Bills journalTmp, sd_journal_close); 814e1f26343SJason M. Bills journalTmp = nullptr; 815193ad2faSJason M. Bills uint64_t entryCount = 0; 816e1f26343SJason M. Bills SD_JOURNAL_FOREACH(journal.get()) 817e1f26343SJason M. Bills { 818193ad2faSJason M. Bills entryCount++; 819193ad2faSJason M. Bills // Handle paging using skip (number of entries to skip from the 820193ad2faSJason M. Bills // start) and top (number of entries to display) 821193ad2faSJason M. Bills if (entryCount <= skip || entryCount > skip + top) 822193ad2faSJason M. Bills { 823193ad2faSJason M. Bills continue; 824193ad2faSJason M. Bills } 825193ad2faSJason M. Bills 82616428a1aSJason M. Bills std::string idStr; 82716428a1aSJason M. Bills if (!getUniqueEntryID(journal.get(), idStr)) 828e1f26343SJason M. Bills { 829e1f26343SJason M. Bills continue; 830e1f26343SJason M. Bills } 831e1f26343SJason M. Bills 832e1f26343SJason M. Bills logEntryArray.push_back({}); 833*c4bf6374SJason M. Bills nlohmann::json &bmcJournalLogEntry = logEntryArray.back(); 834*c4bf6374SJason M. Bills if (fillBMCJournalLogEntryJson(idStr, journal.get(), 835*c4bf6374SJason M. Bills bmcJournalLogEntry) != 0) 836e1f26343SJason M. Bills { 837f12894f8SJason M. Bills messages::internalError(asyncResp->res); 838e1f26343SJason M. Bills return; 839e1f26343SJason M. Bills } 840e1f26343SJason M. Bills } 841193ad2faSJason M. Bills asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 842193ad2faSJason M. Bills if (skip + top < entryCount) 843193ad2faSJason M. Bills { 844193ad2faSJason M. Bills asyncResp->res.jsonValue["Members@odata.nextLink"] = 845*c4bf6374SJason M. Bills "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 846193ad2faSJason M. Bills std::to_string(skip + top); 847193ad2faSJason M. Bills } 848e1f26343SJason M. Bills } 849e1f26343SJason M. Bills }; 850e1f26343SJason M. Bills 851*c4bf6374SJason M. Bills class BMCJournalLogEntry : public Node 852e1f26343SJason M. Bills { 853e1f26343SJason M. Bills public: 854*c4bf6374SJason M. Bills BMCJournalLogEntry(CrowApp &app) : 855*c4bf6374SJason M. Bills Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/", 856e1f26343SJason M. Bills std::string()) 857e1f26343SJason M. Bills { 858e1f26343SJason M. Bills entityPrivileges = { 859e1f26343SJason M. Bills {boost::beast::http::verb::get, {{"Login"}}}, 860e1f26343SJason M. Bills {boost::beast::http::verb::head, {{"Login"}}}, 861e1f26343SJason M. Bills {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 862e1f26343SJason M. Bills {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 863e1f26343SJason M. Bills {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 864e1f26343SJason M. Bills {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 865e1f26343SJason M. Bills } 866e1f26343SJason M. Bills 867e1f26343SJason M. Bills private: 868e1f26343SJason M. Bills void doGet(crow::Response &res, const crow::Request &req, 869e1f26343SJason M. Bills const std::vector<std::string> ¶ms) override 870e1f26343SJason M. Bills { 871e1f26343SJason M. Bills std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 872e1f26343SJason M. Bills if (params.size() != 1) 873e1f26343SJason M. Bills { 874f12894f8SJason M. Bills messages::internalError(asyncResp->res); 875e1f26343SJason M. Bills return; 876e1f26343SJason M. Bills } 87716428a1aSJason M. Bills const std::string &entryID = params[0]; 878e1f26343SJason M. Bills // Convert the unique ID back to a timestamp to find the entry 879e1f26343SJason M. Bills uint64_t ts = 0; 880e1f26343SJason M. Bills uint16_t index = 0; 88116428a1aSJason M. Bills if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 882e1f26343SJason M. Bills { 88316428a1aSJason M. Bills return; 884e1f26343SJason M. Bills } 885e1f26343SJason M. Bills 886e1f26343SJason M. Bills sd_journal *journalTmp = nullptr; 887e1f26343SJason M. Bills int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 888e1f26343SJason M. Bills if (ret < 0) 889e1f26343SJason M. Bills { 890e1f26343SJason M. Bills BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 891f12894f8SJason M. Bills messages::internalError(asyncResp->res); 892e1f26343SJason M. Bills return; 893e1f26343SJason M. Bills } 894e1f26343SJason M. Bills std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 895e1f26343SJason M. Bills journalTmp, sd_journal_close); 896e1f26343SJason M. Bills journalTmp = nullptr; 897e1f26343SJason M. Bills // Go to the timestamp in the log and move to the entry at the index 898e1f26343SJason M. Bills ret = sd_journal_seek_realtime_usec(journal.get(), ts); 899e1f26343SJason M. Bills for (int i = 0; i <= index; i++) 900e1f26343SJason M. Bills { 901e1f26343SJason M. Bills sd_journal_next(journal.get()); 902e1f26343SJason M. Bills } 903*c4bf6374SJason M. Bills // Confirm that the entry ID matches what was requested 904*c4bf6374SJason M. Bills std::string idStr; 905*c4bf6374SJason M. Bills if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID) 906*c4bf6374SJason M. Bills { 907*c4bf6374SJason M. Bills messages::resourceMissingAtURI(asyncResp->res, entryID); 908*c4bf6374SJason M. Bills return; 909*c4bf6374SJason M. Bills } 910*c4bf6374SJason M. Bills 911*c4bf6374SJason M. Bills if (fillBMCJournalLogEntryJson(entryID, journal.get(), 912e1f26343SJason M. Bills asyncResp->res.jsonValue) != 0) 913e1f26343SJason M. Bills { 914f12894f8SJason M. Bills messages::internalError(asyncResp->res); 915e1f26343SJason M. Bills return; 916e1f26343SJason M. Bills } 917e1f26343SJason M. Bills } 918e1f26343SJason M. Bills }; 919e1f26343SJason M. Bills 920e1f26343SJason M. Bills class CPULogService : public Node 921e1f26343SJason M. Bills { 922e1f26343SJason M. Bills public: 923e1f26343SJason M. Bills template <typename CrowApp> 924e1f26343SJason M. Bills CPULogService(CrowApp &app) : 925e1f26343SJason M. Bills Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/") 9261da66f75SEd Tanous { 9271da66f75SEd Tanous // Set the id for SubRoute 9284ed77cd5SEd Tanous Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/CpuLog"; 9291da66f75SEd Tanous entityPrivileges = { 930e1f26343SJason M. Bills {boost::beast::http::verb::get, {{"Login"}}}, 931e1f26343SJason M. Bills {boost::beast::http::verb::head, {{"Login"}}}, 932e1f26343SJason M. Bills {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 933e1f26343SJason M. Bills {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 934e1f26343SJason M. Bills {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 935e1f26343SJason M. Bills {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 9361da66f75SEd Tanous } 9371da66f75SEd Tanous 9381da66f75SEd Tanous private: 9391da66f75SEd Tanous /** 9401da66f75SEd Tanous * Functions triggers appropriate requests on DBus 9411da66f75SEd Tanous */ 9421da66f75SEd Tanous void doGet(crow::Response &res, const crow::Request &req, 9431da66f75SEd Tanous const std::vector<std::string> ¶ms) override 9441da66f75SEd Tanous { 945e1f26343SJason M. Bills std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 9461da66f75SEd Tanous // Copy over the static data to include the entries added by SubRoute 947e1f26343SJason M. Bills asyncResp->res.jsonValue = Node::json; 948e1f26343SJason M. Bills asyncResp->res.jsonValue["@odata.type"] = 949e1f26343SJason M. Bills "#LogService.v1_1_0.LogService"; 950e1f26343SJason M. Bills asyncResp->res.jsonValue["@odata.context"] = 951*c4bf6374SJason M. Bills "/redfish/v1/$metadata#LogService.LogService"; 952e1f26343SJason M. Bills asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service"; 953e1f26343SJason M. Bills asyncResp->res.jsonValue["Description"] = "CPU Log Service"; 954e1f26343SJason M. Bills asyncResp->res.jsonValue["Id"] = "CPU Log"; 955e1f26343SJason M. Bills asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 956e1f26343SJason M. Bills asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 957e1f26343SJason M. Bills asyncResp->res.jsonValue["Actions"] = { 9581da66f75SEd Tanous {"Oem", 9591da66f75SEd Tanous {{"#CpuLog.Immediate", 960*c4bf6374SJason M. Bills {{"target", "/redfish/v1/Managers/bmc/LogServices/CpuLog/" 961*c4bf6374SJason M. Bills "Actions/Oem/CpuLog.Immediate"}}}}}}; 9621da66f75SEd Tanous 9631da66f75SEd Tanous #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI 964e1f26343SJason M. Bills asyncResp->res.jsonValue["Actions"]["Oem"].push_back( 9651da66f75SEd Tanous {"#CpuLog.SendRawPeci", 966*c4bf6374SJason M. Bills {{"target", "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/" 967*c4bf6374SJason M. Bills "Oem/CpuLog.SendRawPeci"}}}); 9681da66f75SEd Tanous #endif 9691da66f75SEd Tanous } 9701da66f75SEd Tanous }; 9711da66f75SEd Tanous 972e1f26343SJason M. Bills class CPULogEntryCollection : public Node 9731da66f75SEd Tanous { 9741da66f75SEd Tanous public: 9751da66f75SEd Tanous template <typename CrowApp> 976e1f26343SJason M. Bills CPULogEntryCollection(CrowApp &app) : 977e1f26343SJason M. Bills Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/") 9781da66f75SEd Tanous { 9791da66f75SEd Tanous // Collections use static ID for SubRoute to add to its parent, but only 9801da66f75SEd Tanous // load dynamic data so the duplicate static members don't get displayed 9811da66f75SEd Tanous Node::json["@odata.id"] = 9824ed77cd5SEd Tanous "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries"; 9831da66f75SEd Tanous entityPrivileges = { 984e1f26343SJason M. Bills {boost::beast::http::verb::get, {{"Login"}}}, 985e1f26343SJason M. Bills {boost::beast::http::verb::head, {{"Login"}}}, 986e1f26343SJason M. Bills {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 987e1f26343SJason M. Bills {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 988e1f26343SJason M. Bills {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 989e1f26343SJason M. Bills {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 9901da66f75SEd Tanous } 9911da66f75SEd Tanous 9921da66f75SEd Tanous private: 9931da66f75SEd Tanous /** 9941da66f75SEd Tanous * Functions triggers appropriate requests on DBus 9951da66f75SEd Tanous */ 9961da66f75SEd Tanous void doGet(crow::Response &res, const crow::Request &req, 9971da66f75SEd Tanous const std::vector<std::string> ¶ms) override 9981da66f75SEd Tanous { 999e1f26343SJason M. Bills std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 10001da66f75SEd Tanous // Collections don't include the static data added by SubRoute because 10011da66f75SEd Tanous // it has a duplicate entry for members 1002e1f26343SJason M. Bills auto getLogEntriesCallback = [asyncResp]( 1003e1f26343SJason M. Bills const boost::system::error_code ec, 10041da66f75SEd Tanous const std::vector<std::string> &resp) { 10051da66f75SEd Tanous if (ec) 10061da66f75SEd Tanous { 10071da66f75SEd Tanous if (ec.value() != 10081da66f75SEd Tanous boost::system::errc::no_such_file_or_directory) 10091da66f75SEd Tanous { 10101da66f75SEd Tanous BMCWEB_LOG_DEBUG << "failed to get entries ec: " 10111da66f75SEd Tanous << ec.message(); 1012f12894f8SJason M. Bills messages::internalError(asyncResp->res); 10131da66f75SEd Tanous return; 10141da66f75SEd Tanous } 10151da66f75SEd Tanous } 1016e1f26343SJason M. Bills asyncResp->res.jsonValue["@odata.type"] = 10171da66f75SEd Tanous "#LogEntryCollection.LogEntryCollection"; 1018e1f26343SJason M. Bills asyncResp->res.jsonValue["@odata.context"] = 1019*c4bf6374SJason M. Bills "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 1020e1f26343SJason M. Bills asyncResp->res.jsonValue["@odata.id"] = 1021e1f26343SJason M. Bills "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries"; 1022e1f26343SJason M. Bills asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries"; 1023e1f26343SJason M. Bills asyncResp->res.jsonValue["Description"] = 1024e1f26343SJason M. Bills "Collection of CPU Log Entries"; 1025e1f26343SJason M. Bills nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1026e1f26343SJason M. Bills logEntryArray = nlohmann::json::array(); 10271da66f75SEd Tanous for (const std::string &objpath : resp) 10281da66f75SEd Tanous { 10291da66f75SEd Tanous // Don't list the immediate log 10304ed77cd5SEd Tanous if (objpath.compare(cpuLogImmediatePath) == 0) 10311da66f75SEd Tanous { 10321da66f75SEd Tanous continue; 10331da66f75SEd Tanous } 10344ed77cd5SEd Tanous std::size_t lastPos = objpath.rfind("/"); 10354ed77cd5SEd Tanous if (lastPos != std::string::npos) 10361da66f75SEd Tanous { 1037e1f26343SJason M. Bills logEntryArray.push_back( 1038e1f26343SJason M. Bills {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/" 1039e1f26343SJason M. Bills "CpuLog/Entries/" + 10404ed77cd5SEd Tanous objpath.substr(lastPos + 1)}}); 10411da66f75SEd Tanous } 10421da66f75SEd Tanous } 1043e1f26343SJason M. Bills asyncResp->res.jsonValue["Members@odata.count"] = 1044e1f26343SJason M. Bills logEntryArray.size(); 10451da66f75SEd Tanous }; 10461da66f75SEd Tanous crow::connections::systemBus->async_method_call( 10471da66f75SEd Tanous std::move(getLogEntriesCallback), 10481da66f75SEd Tanous "xyz.openbmc_project.ObjectMapper", 10491da66f75SEd Tanous "/xyz/openbmc_project/object_mapper", 10501da66f75SEd Tanous "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 10514ed77cd5SEd Tanous std::array<const char *, 1>{cpuLogInterface}); 10521da66f75SEd Tanous } 10531da66f75SEd Tanous }; 10541da66f75SEd Tanous 10551da66f75SEd Tanous std::string getLogCreatedTime(const nlohmann::json &cpuLog) 10561da66f75SEd Tanous { 10571da66f75SEd Tanous nlohmann::json::const_iterator metaIt = cpuLog.find("metadata"); 10581da66f75SEd Tanous if (metaIt != cpuLog.end()) 10591da66f75SEd Tanous { 10601da66f75SEd Tanous nlohmann::json::const_iterator tsIt = metaIt->find("timestamp"); 10611da66f75SEd Tanous if (tsIt != metaIt->end()) 10621da66f75SEd Tanous { 10631da66f75SEd Tanous const std::string *logTime = tsIt->get_ptr<const std::string *>(); 10641da66f75SEd Tanous if (logTime != nullptr) 10651da66f75SEd Tanous { 10661da66f75SEd Tanous return *logTime; 10671da66f75SEd Tanous } 10681da66f75SEd Tanous } 10691da66f75SEd Tanous } 10701da66f75SEd Tanous BMCWEB_LOG_DEBUG << "failed to find log timestamp"; 10711da66f75SEd Tanous 10721da66f75SEd Tanous return std::string(); 10731da66f75SEd Tanous } 10741da66f75SEd Tanous 1075e1f26343SJason M. Bills class CPULogEntry : public Node 10761da66f75SEd Tanous { 10771da66f75SEd Tanous public: 1078e1f26343SJason M. Bills CPULogEntry(CrowApp &app) : 10794ed77cd5SEd Tanous Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/", 10801da66f75SEd Tanous std::string()) 10811da66f75SEd Tanous { 10821da66f75SEd Tanous entityPrivileges = { 1083e1f26343SJason M. Bills {boost::beast::http::verb::get, {{"Login"}}}, 1084e1f26343SJason M. Bills {boost::beast::http::verb::head, {{"Login"}}}, 1085e1f26343SJason M. Bills {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1086e1f26343SJason M. Bills {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1087e1f26343SJason M. Bills {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1088e1f26343SJason M. Bills {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 10891da66f75SEd Tanous } 10901da66f75SEd Tanous 10911da66f75SEd Tanous private: 10921da66f75SEd Tanous void doGet(crow::Response &res, const crow::Request &req, 10931da66f75SEd Tanous const std::vector<std::string> ¶ms) override 10941da66f75SEd Tanous { 1095e1f26343SJason M. Bills std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 10961da66f75SEd Tanous if (params.size() != 1) 10971da66f75SEd Tanous { 1098f12894f8SJason M. Bills messages::internalError(asyncResp->res); 10991da66f75SEd Tanous return; 11001da66f75SEd Tanous } 11014ed77cd5SEd Tanous const uint8_t logId = std::atoi(params[0].c_str()); 1102e1f26343SJason M. Bills auto getStoredLogCallback = 1103e1f26343SJason M. Bills [asyncResp, 11044ed77cd5SEd Tanous logId](const boost::system::error_code ec, 1105e1f26343SJason M. Bills const sdbusplus::message::variant<std::string> &resp) { 11061da66f75SEd Tanous if (ec) 11071da66f75SEd Tanous { 1108e1f26343SJason M. Bills BMCWEB_LOG_DEBUG << "failed to get log ec: " 1109e1f26343SJason M. Bills << ec.message(); 1110f12894f8SJason M. Bills messages::internalError(asyncResp->res); 11111da66f75SEd Tanous return; 11121da66f75SEd Tanous } 1113e1f26343SJason M. Bills const std::string *log = 1114e1f26343SJason M. Bills mapbox::getPtr<const std::string>(resp); 11151da66f75SEd Tanous if (log == nullptr) 11161da66f75SEd Tanous { 1117f12894f8SJason M. Bills messages::internalError(asyncResp->res); 11181da66f75SEd Tanous return; 11191da66f75SEd Tanous } 11201da66f75SEd Tanous nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 11211da66f75SEd Tanous if (j.is_discarded()) 11221da66f75SEd Tanous { 1123f12894f8SJason M. Bills messages::internalError(asyncResp->res); 11241da66f75SEd Tanous return; 11251da66f75SEd Tanous } 11261da66f75SEd Tanous std::string t = getLogCreatedTime(j); 1127e1f26343SJason M. Bills asyncResp->res.jsonValue = { 11281da66f75SEd Tanous {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 1129e1f26343SJason M. Bills {"@odata.context", 1130e1f26343SJason M. Bills "/redfish/v1/$metadata#LogEntry.LogEntry"}, 11311da66f75SEd Tanous {"@odata.id", 11324ed77cd5SEd Tanous "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" + 11334ed77cd5SEd Tanous std::to_string(logId)}, 11341da66f75SEd Tanous {"Name", "CPU Debug Log"}, 11354ed77cd5SEd Tanous {"Id", logId}, 11361da66f75SEd Tanous {"EntryType", "Oem"}, 11371da66f75SEd Tanous {"OemRecordFormat", "Intel CPU Log"}, 11381da66f75SEd Tanous {"Oem", {{"Intel", std::move(j)}}}, 11391da66f75SEd Tanous {"Created", std::move(t)}}; 11401da66f75SEd Tanous }; 11411da66f75SEd Tanous crow::connections::systemBus->async_method_call( 11424ed77cd5SEd Tanous std::move(getStoredLogCallback), cpuLogObject, 11434ed77cd5SEd Tanous cpuLogPath + std::string("/") + std::to_string(logId), 11444ed77cd5SEd Tanous "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log"); 11451da66f75SEd Tanous } 11461da66f75SEd Tanous }; 11471da66f75SEd Tanous 1148e1f26343SJason M. Bills class ImmediateCPULog : public Node 11491da66f75SEd Tanous { 11501da66f75SEd Tanous public: 1151e1f26343SJason M. Bills ImmediateCPULog(CrowApp &app) : 11524ed77cd5SEd Tanous Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 1153e1f26343SJason M. Bills "CpuLog.Immediate/") 11541da66f75SEd Tanous { 11551da66f75SEd Tanous entityPrivileges = { 1156e1f26343SJason M. Bills {boost::beast::http::verb::get, {{"Login"}}}, 1157e1f26343SJason M. Bills {boost::beast::http::verb::head, {{"Login"}}}, 1158e1f26343SJason M. Bills {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1159e1f26343SJason M. Bills {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1160e1f26343SJason M. Bills {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1161e1f26343SJason M. Bills {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 11621da66f75SEd Tanous } 11631da66f75SEd Tanous 11641da66f75SEd Tanous private: 11651da66f75SEd Tanous void doPost(crow::Response &res, const crow::Request &req, 11661da66f75SEd Tanous const std::vector<std::string> ¶ms) override 11671da66f75SEd Tanous { 1168e1f26343SJason M. Bills std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 11691da66f75SEd Tanous static std::unique_ptr<sdbusplus::bus::match::match> 11701da66f75SEd Tanous immediateLogMatcher; 11711da66f75SEd Tanous 11721da66f75SEd Tanous // Only allow one Immediate Log request at a time 11731da66f75SEd Tanous if (immediateLogMatcher != nullptr) 11741da66f75SEd Tanous { 1175e1f26343SJason M. Bills asyncResp->res.addHeader("Retry-After", "30"); 1176f12894f8SJason M. Bills messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 11771da66f75SEd Tanous return; 11781da66f75SEd Tanous } 11791da66f75SEd Tanous // Make this static so it survives outside this method 11801da66f75SEd Tanous static boost::asio::deadline_timer timeout(*req.ioService); 11811da66f75SEd Tanous 11821da66f75SEd Tanous timeout.expires_from_now(boost::posix_time::seconds(30)); 1183e1f26343SJason M. Bills timeout.async_wait([asyncResp](const boost::system::error_code &ec) { 11841da66f75SEd Tanous immediateLogMatcher = nullptr; 11851da66f75SEd Tanous if (ec) 11861da66f75SEd Tanous { 11871da66f75SEd Tanous // operation_aborted is expected if timer is canceled before 11881da66f75SEd Tanous // completion. 11891da66f75SEd Tanous if (ec != boost::asio::error::operation_aborted) 11901da66f75SEd Tanous { 11911da66f75SEd Tanous BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 11921da66f75SEd Tanous } 11931da66f75SEd Tanous return; 11941da66f75SEd Tanous } 11951da66f75SEd Tanous BMCWEB_LOG_ERROR << "Timed out waiting for immediate log"; 11961da66f75SEd Tanous 1197f12894f8SJason M. Bills messages::internalError(asyncResp->res); 11981da66f75SEd Tanous }); 11991da66f75SEd Tanous 1200e1f26343SJason M. Bills auto immediateLogMatcherCallback = [asyncResp]( 12011da66f75SEd Tanous sdbusplus::message::message &m) { 12021da66f75SEd Tanous BMCWEB_LOG_DEBUG << "Immediate log available match fired"; 12031da66f75SEd Tanous boost::system::error_code ec; 12041da66f75SEd Tanous timeout.cancel(ec); 12051da66f75SEd Tanous if (ec) 12061da66f75SEd Tanous { 12071da66f75SEd Tanous BMCWEB_LOG_ERROR << "error canceling timer " << ec; 12081da66f75SEd Tanous } 12094ed77cd5SEd Tanous sdbusplus::message::object_path objPath; 12101da66f75SEd Tanous boost::container::flat_map< 12111da66f75SEd Tanous std::string, 12121da66f75SEd Tanous boost::container::flat_map< 12131da66f75SEd Tanous std::string, sdbusplus::message::variant<std::string>>> 12144ed77cd5SEd Tanous interfacesAdded; 12154ed77cd5SEd Tanous m.read(objPath, interfacesAdded); 12161da66f75SEd Tanous const std::string *log = mapbox::getPtr<const std::string>( 12174ed77cd5SEd Tanous interfacesAdded[cpuLogInterface]["Log"]); 12181da66f75SEd Tanous if (log == nullptr) 12191da66f75SEd Tanous { 1220f12894f8SJason M. Bills messages::internalError(asyncResp->res); 12211da66f75SEd Tanous // Careful with immediateLogMatcher. It is a unique_ptr to the 12221da66f75SEd Tanous // match object inside which this lambda is executing. Once it 12231da66f75SEd Tanous // is set to nullptr, the match object will be destroyed and the 12241da66f75SEd Tanous // lambda will lose its context, including res, so it needs to 12251da66f75SEd Tanous // be the last thing done. 12261da66f75SEd Tanous immediateLogMatcher = nullptr; 12271da66f75SEd Tanous return; 12281da66f75SEd Tanous } 12291da66f75SEd Tanous nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 12301da66f75SEd Tanous if (j.is_discarded()) 12311da66f75SEd Tanous { 1232f12894f8SJason M. Bills messages::internalError(asyncResp->res); 12331da66f75SEd Tanous // Careful with immediateLogMatcher. It is a unique_ptr to the 12341da66f75SEd Tanous // match object inside which this lambda is executing. Once it 12351da66f75SEd Tanous // is set to nullptr, the match object will be destroyed and the 12361da66f75SEd Tanous // lambda will lose its context, including res, so it needs to 12371da66f75SEd Tanous // be the last thing done. 12381da66f75SEd Tanous immediateLogMatcher = nullptr; 12391da66f75SEd Tanous return; 12401da66f75SEd Tanous } 12411da66f75SEd Tanous std::string t = getLogCreatedTime(j); 1242e1f26343SJason M. Bills asyncResp->res.jsonValue = { 12431da66f75SEd Tanous {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 12441da66f75SEd Tanous {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 12451da66f75SEd Tanous {"Name", "CPU Debug Log"}, 12461da66f75SEd Tanous {"EntryType", "Oem"}, 12471da66f75SEd Tanous {"OemRecordFormat", "Intel CPU Log"}, 12481da66f75SEd Tanous {"Oem", {{"Intel", std::move(j)}}}, 12491da66f75SEd Tanous {"Created", std::move(t)}}; 12501da66f75SEd Tanous // Careful with immediateLogMatcher. It is a unique_ptr to the 12511da66f75SEd Tanous // match object inside which this lambda is executing. Once it is 12521da66f75SEd Tanous // set to nullptr, the match object will be destroyed and the lambda 12531da66f75SEd Tanous // will lose its context, including res, so it needs to be the last 12541da66f75SEd Tanous // thing done. 12551da66f75SEd Tanous immediateLogMatcher = nullptr; 12561da66f75SEd Tanous }; 12571da66f75SEd Tanous immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>( 12581da66f75SEd Tanous *crow::connections::systemBus, 12591da66f75SEd Tanous sdbusplus::bus::match::rules::interfacesAdded() + 12604ed77cd5SEd Tanous sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath), 12611da66f75SEd Tanous std::move(immediateLogMatcherCallback)); 12621da66f75SEd Tanous 12631da66f75SEd Tanous auto generateImmediateLogCallback = 1264e1f26343SJason M. Bills [asyncResp](const boost::system::error_code ec, 12651da66f75SEd Tanous const std::string &resp) { 12661da66f75SEd Tanous if (ec) 12671da66f75SEd Tanous { 12681da66f75SEd Tanous if (ec.value() == 12691da66f75SEd Tanous boost::system::errc::operation_not_supported) 12701da66f75SEd Tanous { 1271f12894f8SJason M. Bills messages::resourceInStandby(asyncResp->res); 12721da66f75SEd Tanous } 12731da66f75SEd Tanous else 12741da66f75SEd Tanous { 1275f12894f8SJason M. Bills messages::internalError(asyncResp->res); 12761da66f75SEd Tanous } 12771da66f75SEd Tanous boost::system::error_code timeoutec; 12781da66f75SEd Tanous timeout.cancel(timeoutec); 12791da66f75SEd Tanous if (timeoutec) 12801da66f75SEd Tanous { 12811da66f75SEd Tanous BMCWEB_LOG_ERROR << "error canceling timer " 12821da66f75SEd Tanous << timeoutec; 12831da66f75SEd Tanous } 12841da66f75SEd Tanous immediateLogMatcher = nullptr; 12851da66f75SEd Tanous return; 12861da66f75SEd Tanous } 12871da66f75SEd Tanous }; 12881da66f75SEd Tanous crow::connections::systemBus->async_method_call( 12894ed77cd5SEd Tanous std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath, 12904ed77cd5SEd Tanous cpuLogImmediateInterface, "GenerateImmediateLog"); 12911da66f75SEd Tanous } 12921da66f75SEd Tanous }; 12931da66f75SEd Tanous 1294e1f26343SJason M. Bills class SendRawPECI : public Node 12951da66f75SEd Tanous { 12961da66f75SEd Tanous public: 1297e1f26343SJason M. Bills SendRawPECI(CrowApp &app) : 12984ed77cd5SEd Tanous Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 1299e1f26343SJason M. Bills "CpuLog.SendRawPeci/") 13001da66f75SEd Tanous { 13011da66f75SEd Tanous entityPrivileges = { 13021da66f75SEd Tanous {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 13031da66f75SEd Tanous {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 13041da66f75SEd Tanous {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 13051da66f75SEd Tanous {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 13061da66f75SEd Tanous {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 13071da66f75SEd Tanous {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 13081da66f75SEd Tanous } 13091da66f75SEd Tanous 13101da66f75SEd Tanous private: 13111da66f75SEd Tanous void doPost(crow::Response &res, const crow::Request &req, 13121da66f75SEd Tanous const std::vector<std::string> ¶ms) override 13131da66f75SEd Tanous { 1314e1f26343SJason M. Bills std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 13151da66f75SEd Tanous // Get the Raw PECI command from the request 1316e1f26343SJason M. Bills nlohmann::json rawPECICmd; 1317e1f26343SJason M. Bills if (!json_util::processJsonFromRequest(res, req, rawPECICmd)) 13181da66f75SEd Tanous { 13191da66f75SEd Tanous return; 13201da66f75SEd Tanous } 13211da66f75SEd Tanous // Get the Client Address from the request 1322e1f26343SJason M. Bills nlohmann::json::const_iterator caIt = rawPECICmd.find("ClientAddress"); 1323e1f26343SJason M. Bills if (caIt == rawPECICmd.end()) 13241da66f75SEd Tanous { 1325a08b46ccSJason M. Bills messages::propertyMissing(asyncResp->res, "ClientAddress"); 13261da66f75SEd Tanous return; 13271da66f75SEd Tanous } 13281da66f75SEd Tanous const uint64_t *ca = caIt->get_ptr<const uint64_t *>(); 13291da66f75SEd Tanous if (ca == nullptr) 13301da66f75SEd Tanous { 1331f12894f8SJason M. Bills messages::propertyValueTypeError(asyncResp->res, caIt->dump(), 1332a08b46ccSJason M. Bills "ClientAddress"); 13331da66f75SEd Tanous return; 13341da66f75SEd Tanous } 13351da66f75SEd Tanous // Get the Read Length from the request 13361da66f75SEd Tanous const uint8_t clientAddress = static_cast<uint8_t>(*ca); 1337e1f26343SJason M. Bills nlohmann::json::const_iterator rlIt = rawPECICmd.find("ReadLength"); 1338e1f26343SJason M. Bills if (rlIt == rawPECICmd.end()) 13391da66f75SEd Tanous { 1340a08b46ccSJason M. Bills messages::propertyMissing(asyncResp->res, "ReadLength"); 13411da66f75SEd Tanous return; 13421da66f75SEd Tanous } 13431da66f75SEd Tanous const uint64_t *rl = rlIt->get_ptr<const uint64_t *>(); 13441da66f75SEd Tanous if (rl == nullptr) 13451da66f75SEd Tanous { 1346f12894f8SJason M. Bills messages::propertyValueTypeError(asyncResp->res, rlIt->dump(), 1347a08b46ccSJason M. Bills "ReadLength"); 13481da66f75SEd Tanous return; 13491da66f75SEd Tanous } 13501da66f75SEd Tanous // Get the PECI Command from the request 13511da66f75SEd Tanous const uint32_t readLength = static_cast<uint32_t>(*rl); 1352e1f26343SJason M. Bills nlohmann::json::const_iterator pcIt = rawPECICmd.find("PECICommand"); 1353e1f26343SJason M. Bills if (pcIt == rawPECICmd.end()) 13541da66f75SEd Tanous { 1355a08b46ccSJason M. Bills messages::propertyMissing(asyncResp->res, "PECICommand"); 13561da66f75SEd Tanous return; 13571da66f75SEd Tanous } 13581da66f75SEd Tanous std::vector<uint8_t> peciCommand; 13591da66f75SEd Tanous for (auto pc : *pcIt) 13601da66f75SEd Tanous { 13611da66f75SEd Tanous const uint64_t *val = pc.get_ptr<const uint64_t *>(); 13621da66f75SEd Tanous if (val == nullptr) 13631da66f75SEd Tanous { 13641da66f75SEd Tanous messages::propertyValueTypeError( 1365f12894f8SJason M. Bills asyncResp->res, pc.dump(), 1366a08b46ccSJason M. Bills "PECICommand/" + std::to_string(peciCommand.size())); 13671da66f75SEd Tanous return; 13681da66f75SEd Tanous } 13691da66f75SEd Tanous peciCommand.push_back(static_cast<uint8_t>(*val)); 13701da66f75SEd Tanous } 13711da66f75SEd Tanous // Callback to return the Raw PECI response 1372e1f26343SJason M. Bills auto sendRawPECICallback = 1373e1f26343SJason M. Bills [asyncResp](const boost::system::error_code ec, 13741da66f75SEd Tanous const std::vector<uint8_t> &resp) { 13751da66f75SEd Tanous if (ec) 13761da66f75SEd Tanous { 13771da66f75SEd Tanous BMCWEB_LOG_DEBUG << "failed to send PECI command ec: " 13781da66f75SEd Tanous << ec.message(); 1379f12894f8SJason M. Bills messages::internalError(asyncResp->res); 13801da66f75SEd Tanous return; 13811da66f75SEd Tanous } 1382e1f26343SJason M. Bills asyncResp->res.jsonValue = {{"Name", "PECI Command Response"}, 13831da66f75SEd Tanous {"PECIResponse", resp}}; 13841da66f75SEd Tanous }; 13851da66f75SEd Tanous // Call the SendRawPECI command with the provided data 13861da66f75SEd Tanous crow::connections::systemBus->async_method_call( 1387e1f26343SJason M. Bills std::move(sendRawPECICallback), cpuLogObject, cpuLogPath, 1388e1f26343SJason M. Bills cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength, 13894ed77cd5SEd Tanous peciCommand); 13901da66f75SEd Tanous } 13911da66f75SEd Tanous }; 13921da66f75SEd Tanous 13931da66f75SEd Tanous } // namespace redfish 1394