blob: a2d083ac1418e1b2f228e90ae61c97af0bebf465 [file] [log] [blame]
Ed Tanous1da66f72018-07-27 16:13:37 -07001/*
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"
Jason M. Bills4851d452019-03-28 11:27:48 -070019#include "registries.hpp"
20#include "registries/base_message_registry.hpp"
21#include "registries/openbmc_message_registry.hpp"
Ed Tanous1da66f72018-07-27 16:13:37 -070022
Jason M. Billse1f26342018-07-18 12:12:00 -070023#include <systemd/sd-journal.h>
24
Jason M. Bills4851d452019-03-28 11:27:48 -070025#include <boost/algorithm/string/split.hpp>
26#include <boost/beast/core/span.hpp>
Ed Tanous1da66f72018-07-27 16:13:37 -070027#include <boost/container/flat_map.hpp>
Andrew Geisslercb92c032018-08-17 07:56:14 -070028#include <error_messages.hpp>
James Feist4418c7f2019-04-15 11:09:15 -070029#include <filesystem>
Jason M. Billscd225da2019-05-08 15:31:57 -070030#include <string_view>
Ed Tanousabf2add2019-01-22 16:40:12 -080031#include <variant>
Ed Tanous1da66f72018-07-27 16:13:37 -070032
33namespace redfish
34{
35
Jason M. Bills424c4172019-03-21 13:50:33 -070036constexpr char const *CrashdumpObject = "com.intel.crashdump";
37constexpr char const *CrashdumpPath = "/com/intel/crashdump";
38constexpr char const *CrashdumpOnDemandPath = "/com/intel/crashdump/OnDemand";
39constexpr char const *CrashdumpInterface = "com.intel.crashdump";
40constexpr char const *CrashdumpOnDemandInterface =
41 "com.intel.crashdump.OnDemand";
42constexpr char const *CrashdumpRawPECIInterface =
43 "com.intel.crashdump.SendRawPeci";
Ed Tanous1da66f72018-07-27 16:13:37 -070044
Jason M. Bills4851d452019-03-28 11:27:48 -070045namespace message_registries
46{
47static const Message *getMessageFromRegistry(
48 const std::string &messageKey,
49 const boost::beast::span<const MessageEntry> registry)
50{
51 boost::beast::span<const MessageEntry>::const_iterator messageIt =
52 std::find_if(registry.cbegin(), registry.cend(),
53 [&messageKey](const MessageEntry &messageEntry) {
54 return !std::strcmp(messageEntry.first,
55 messageKey.c_str());
56 });
57 if (messageIt != registry.cend())
58 {
59 return &messageIt->second;
60 }
61
62 return nullptr;
63}
64
65static const Message *getMessage(const std::string_view &messageID)
66{
67 // Redfish MessageIds are in the form
68 // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find
69 // the right Message
70 std::vector<std::string> fields;
71 fields.reserve(4);
72 boost::split(fields, messageID, boost::is_any_of("."));
73 std::string &registryName = fields[0];
74 std::string &messageKey = fields[3];
75
76 // Find the right registry and check it for the MessageKey
77 if (std::string(base::header.registryPrefix) == registryName)
78 {
79 return getMessageFromRegistry(
80 messageKey, boost::beast::span<const MessageEntry>(base::registry));
81 }
82 if (std::string(openbmc::header.registryPrefix) == registryName)
83 {
84 return getMessageFromRegistry(
85 messageKey,
86 boost::beast::span<const MessageEntry>(openbmc::registry));
87 }
88 return nullptr;
89}
90} // namespace message_registries
91
James Feistf6150402019-01-08 10:36:20 -080092namespace fs = std::filesystem;
Ed Tanous1da66f72018-07-27 16:13:37 -070093
Andrew Geisslercb92c032018-08-17 07:56:14 -070094using GetManagedPropertyType = boost::container::flat_map<
95 std::string,
96 sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t,
97 int32_t, uint32_t, int64_t, uint64_t, double>>;
98
99using GetManagedObjectsType = boost::container::flat_map<
100 sdbusplus::message::object_path,
101 boost::container::flat_map<std::string, GetManagedPropertyType>>;
102
103inline std::string translateSeverityDbusToRedfish(const std::string &s)
104{
105 if (s == "xyz.openbmc_project.Logging.Entry.Level.Alert")
106 {
107 return "Critical";
108 }
109 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Critical")
110 {
111 return "Critical";
112 }
113 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Debug")
114 {
115 return "OK";
116 }
117 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency")
118 {
119 return "Critical";
120 }
121 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Error")
122 {
123 return "Critical";
124 }
125 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Informational")
126 {
127 return "OK";
128 }
129 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Notice")
130 {
131 return "OK";
132 }
133 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
134 {
135 return "Warning";
136 }
137 return "";
138}
139
Jason M. Bills16428a12018-11-02 12:42:29 -0700140static int getJournalMetadata(sd_journal *journal,
Ed Tanous39e77502019-03-04 17:35:53 -0800141 const std::string_view &field,
142 std::string_view &contents)
Jason M. Bills16428a12018-11-02 12:42:29 -0700143{
144 const char *data = nullptr;
145 size_t length = 0;
146 int ret = 0;
147 // Get the metadata from the requested field of the journal entry
Ed Tanousb01bf292019-03-25 19:25:26 +0000148 ret = sd_journal_get_data(journal, field.data(), (const void **)&data,
149 &length);
Jason M. Bills16428a12018-11-02 12:42:29 -0700150 if (ret < 0)
151 {
152 return ret;
153 }
Ed Tanous39e77502019-03-04 17:35:53 -0800154 contents = std::string_view(data, length);
Jason M. Bills16428a12018-11-02 12:42:29 -0700155 // Only use the content after the "=" character.
156 contents.remove_prefix(std::min(contents.find("=") + 1, contents.size()));
157 return ret;
158}
159
160static int getJournalMetadata(sd_journal *journal,
Ed Tanous39e77502019-03-04 17:35:53 -0800161 const std::string_view &field, const int &base,
Jason M. Bills16428a12018-11-02 12:42:29 -0700162 int &contents)
163{
164 int ret = 0;
Ed Tanous39e77502019-03-04 17:35:53 -0800165 std::string_view metadata;
Jason M. Bills16428a12018-11-02 12:42:29 -0700166 // Get the metadata from the requested field of the journal entry
167 ret = getJournalMetadata(journal, field, metadata);
168 if (ret < 0)
169 {
170 return ret;
171 }
Ed Tanousb01bf292019-03-25 19:25:26 +0000172 contents = strtol(metadata.data(), nullptr, base);
Jason M. Bills16428a12018-11-02 12:42:29 -0700173 return ret;
174}
175
176static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp)
177{
178 int ret = 0;
179 uint64_t timestamp = 0;
180 ret = sd_journal_get_realtime_usec(journal, &timestamp);
181 if (ret < 0)
182 {
183 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
184 << strerror(-ret);
185 return false;
186 }
187 time_t t =
188 static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s
189 struct tm *loctime = localtime(&t);
190 char entryTime[64] = {};
191 if (NULL != loctime)
192 {
193 strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime);
194 }
195 // Insert the ':' into the timezone
Ed Tanous39e77502019-03-04 17:35:53 -0800196 std::string_view t1(entryTime);
197 std::string_view t2(entryTime);
Jason M. Bills16428a12018-11-02 12:42:29 -0700198 if (t1.size() > 2 && t2.size() > 2)
199 {
200 t1.remove_suffix(2);
201 t2.remove_prefix(t2.size() - 2);
202 }
Ed Tanous39e77502019-03-04 17:35:53 -0800203 entryTimestamp = std::string(t1) + ":" + std::string(t2);
Jason M. Bills16428a12018-11-02 12:42:29 -0700204 return true;
205}
206
207static bool getSkipParam(crow::Response &res, const crow::Request &req,
208 long &skip)
209{
210 char *skipParam = req.urlParams.get("$skip");
211 if (skipParam != nullptr)
212 {
213 char *ptr = nullptr;
214 skip = std::strtol(skipParam, &ptr, 10);
215 if (*skipParam == '\0' || *ptr != '\0')
216 {
217
218 messages::queryParameterValueTypeError(res, std::string(skipParam),
219 "$skip");
220 return false;
221 }
222 if (skip < 0)
223 {
224
225 messages::queryParameterOutOfRange(res, std::to_string(skip),
226 "$skip", "greater than 0");
227 return false;
228 }
229 }
230 return true;
231}
232
233static constexpr const long maxEntriesPerPage = 1000;
234static bool getTopParam(crow::Response &res, const crow::Request &req,
235 long &top)
236{
237 char *topParam = req.urlParams.get("$top");
238 if (topParam != nullptr)
239 {
240 char *ptr = nullptr;
241 top = std::strtol(topParam, &ptr, 10);
242 if (*topParam == '\0' || *ptr != '\0')
243 {
244 messages::queryParameterValueTypeError(res, std::string(topParam),
245 "$top");
246 return false;
247 }
248 if (top < 1 || top > maxEntriesPerPage)
249 {
250
251 messages::queryParameterOutOfRange(
252 res, std::to_string(top), "$top",
253 "1-" + std::to_string(maxEntriesPerPage));
254 return false;
255 }
256 }
257 return true;
258}
259
260static bool getUniqueEntryID(sd_journal *journal, std::string &entryID)
261{
262 int ret = 0;
263 static uint64_t prevTs = 0;
264 static int index = 0;
265 // Get the entry timestamp
266 uint64_t curTs = 0;
267 ret = sd_journal_get_realtime_usec(journal, &curTs);
268 if (ret < 0)
269 {
270 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
271 << strerror(-ret);
272 return false;
273 }
274 // If the timestamp isn't unique, increment the index
275 if (curTs == prevTs)
276 {
277 index++;
278 }
279 else
280 {
281 // Otherwise, reset it
282 index = 0;
283 }
284 // Save the timestamp
285 prevTs = curTs;
286
287 entryID = std::to_string(curTs);
288 if (index > 0)
289 {
290 entryID += "_" + std::to_string(index);
291 }
292 return true;
293}
294
Jason M. Bills95820182019-04-22 16:25:34 -0700295static bool getUniqueEntryID(const std::string &logEntry, std::string &entryID)
296{
297 static uint64_t prevTs = 0;
298 static int index = 0;
299 // Get the entry timestamp
300 uint64_t curTs = 0;
301 std::tm timeStruct = {};
302 std::istringstream entryStream(logEntry);
303 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
304 {
305 curTs = std::mktime(&timeStruct);
306 }
307 // If the timestamp isn't unique, increment the index
308 if (curTs == prevTs)
309 {
310 index++;
311 }
312 else
313 {
314 // Otherwise, reset it
315 index = 0;
316 }
317 // Save the timestamp
318 prevTs = curTs;
319
320 entryID = std::to_string(curTs);
321 if (index > 0)
322 {
323 entryID += "_" + std::to_string(index);
324 }
325 return true;
326}
327
Jason M. Bills16428a12018-11-02 12:42:29 -0700328static bool getTimestampFromID(crow::Response &res, const std::string &entryID,
329 uint64_t &timestamp, uint16_t &index)
330{
331 if (entryID.empty())
332 {
333 return false;
334 }
335 // Convert the unique ID back to a timestamp to find the entry
Ed Tanous39e77502019-03-04 17:35:53 -0800336 std::string_view tsStr(entryID);
Jason M. Bills16428a12018-11-02 12:42:29 -0700337
338 auto underscorePos = tsStr.find("_");
339 if (underscorePos != tsStr.npos)
340 {
341 // Timestamp has an index
342 tsStr.remove_suffix(tsStr.size() - underscorePos);
Ed Tanous39e77502019-03-04 17:35:53 -0800343 std::string_view indexStr(entryID);
Jason M. Bills16428a12018-11-02 12:42:29 -0700344 indexStr.remove_prefix(underscorePos + 1);
345 std::size_t pos;
346 try
347 {
Ed Tanous39e77502019-03-04 17:35:53 -0800348 index = std::stoul(std::string(indexStr), &pos);
Jason M. Bills16428a12018-11-02 12:42:29 -0700349 }
Ed Tanousb01bf292019-03-25 19:25:26 +0000350 catch (std::invalid_argument)
Jason M. Bills16428a12018-11-02 12:42:29 -0700351 {
352 messages::resourceMissingAtURI(res, entryID);
353 return false;
354 }
Ed Tanousb01bf292019-03-25 19:25:26 +0000355 catch (std::out_of_range)
Jason M. Bills16428a12018-11-02 12:42:29 -0700356 {
357 messages::resourceMissingAtURI(res, entryID);
358 return false;
359 }
360 if (pos != indexStr.size())
361 {
362 messages::resourceMissingAtURI(res, entryID);
363 return false;
364 }
365 }
366 // Timestamp has no index
367 std::size_t pos;
368 try
369 {
Ed Tanous39e77502019-03-04 17:35:53 -0800370 timestamp = std::stoull(std::string(tsStr), &pos);
Jason M. Bills16428a12018-11-02 12:42:29 -0700371 }
Ed Tanousb01bf292019-03-25 19:25:26 +0000372 catch (std::invalid_argument)
Jason M. Bills16428a12018-11-02 12:42:29 -0700373 {
374 messages::resourceMissingAtURI(res, entryID);
375 return false;
376 }
Ed Tanousb01bf292019-03-25 19:25:26 +0000377 catch (std::out_of_range)
Jason M. Bills16428a12018-11-02 12:42:29 -0700378 {
379 messages::resourceMissingAtURI(res, entryID);
380 return false;
381 }
382 if (pos != tsStr.size())
383 {
384 messages::resourceMissingAtURI(res, entryID);
385 return false;
386 }
387 return true;
388}
389
Jason M. Bills95820182019-04-22 16:25:34 -0700390static bool
391 getRedfishLogFiles(std::vector<std::filesystem::path> &redfishLogFiles)
392{
393 static const std::filesystem::path redfishLogDir = "/var/log";
394 static const std::string redfishLogFilename = "redfish";
395
396 // Loop through the directory looking for redfish log files
397 for (const std::filesystem::directory_entry &dirEnt :
398 std::filesystem::directory_iterator(redfishLogDir))
399 {
400 // If we find a redfish log file, save the path
401 std::string filename = dirEnt.path().filename();
402 if (boost::starts_with(filename, redfishLogFilename))
403 {
404 redfishLogFiles.emplace_back(redfishLogDir / filename);
405 }
406 }
407 // As the log files rotate, they are appended with a ".#" that is higher for
408 // the older logs. Since we don't expect more than 10 log files, we
409 // can just sort the list to get them in order from newest to oldest
410 std::sort(redfishLogFiles.begin(), redfishLogFiles.end());
411
412 return !redfishLogFiles.empty();
413}
414
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800415class SystemLogServiceCollection : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700416{
417 public:
418 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800419 SystemLogServiceCollection(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800420 Node(app, "/redfish/v1/Systems/system/LogServices/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800421 {
422 entityPrivileges = {
423 {boost::beast::http::verb::get, {{"Login"}}},
424 {boost::beast::http::verb::head, {{"Login"}}},
425 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
426 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
427 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
428 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
429 }
430
431 private:
432 /**
433 * Functions triggers appropriate requests on DBus
434 */
435 void doGet(crow::Response &res, const crow::Request &req,
436 const std::vector<std::string> &params) override
437 {
438 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800439 // Collections don't include the static data added by SubRoute because
440 // it has a duplicate entry for members
441 asyncResp->res.jsonValue["@odata.type"] =
442 "#LogServiceCollection.LogServiceCollection";
443 asyncResp->res.jsonValue["@odata.context"] =
444 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
445 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800446 "/redfish/v1/Systems/system/LogServices";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800447 asyncResp->res.jsonValue["Name"] = "System Log Services Collection";
448 asyncResp->res.jsonValue["Description"] =
449 "Collection of LogServices for this Computer System";
450 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
451 logServiceArray = nlohmann::json::array();
Ed Tanous029573d2019-02-01 10:57:49 -0800452 logServiceArray.push_back(
453 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}});
Jason M. Billsd53dd412019-02-12 17:16:22 -0800454#ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
455 logServiceArray.push_back(
Andrew Geisslercb92c032018-08-17 07:56:14 -0700456 {{ "@odata.id",
Jason M. Bills424c4172019-03-21 13:50:33 -0700457 "/redfish/v1/Systems/system/LogServices/Crashdump" }});
Jason M. Billsd53dd412019-02-12 17:16:22 -0800458#endif
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800459 asyncResp->res.jsonValue["Members@odata.count"] =
460 logServiceArray.size();
461 }
462};
463
464class EventLogService : public Node
465{
466 public:
467 template <typename CrowApp>
468 EventLogService(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800469 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800470 {
471 entityPrivileges = {
472 {boost::beast::http::verb::get, {{"Login"}}},
473 {boost::beast::http::verb::head, {{"Login"}}},
474 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
475 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
476 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
477 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
478 }
479
480 private:
481 void doGet(crow::Response &res, const crow::Request &req,
482 const std::vector<std::string> &params) override
483 {
484 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
485
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800486 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800487 "/redfish/v1/Systems/system/LogServices/EventLog";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800488 asyncResp->res.jsonValue["@odata.type"] =
489 "#LogService.v1_1_0.LogService";
490 asyncResp->res.jsonValue["@odata.context"] =
491 "/redfish/v1/$metadata#LogService.LogService";
492 asyncResp->res.jsonValue["Name"] = "Event Log Service";
493 asyncResp->res.jsonValue["Description"] = "System Event Log Service";
494 asyncResp->res.jsonValue["Id"] = "Event Log";
495 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
496 asyncResp->res.jsonValue["Entries"] = {
497 {"@odata.id",
Ed Tanous029573d2019-02-01 10:57:49 -0800498 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}};
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800499 }
500};
501
Jason M. Bills95820182019-04-22 16:25:34 -0700502static int fillEventLogEntryJson(const std::string &logEntryID,
503 const std::string logEntry,
504 nlohmann::json &logEntryJson)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800505{
Jason M. Bills95820182019-04-22 16:25:34 -0700506 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
Jason M. Billscd225da2019-05-08 15:31:57 -0700507 // First get the Timestamp
508 size_t space = logEntry.find_first_of(" ");
509 if (space == std::string::npos)
Jason M. Bills95820182019-04-22 16:25:34 -0700510 {
511 return 1;
512 }
Jason M. Billscd225da2019-05-08 15:31:57 -0700513 std::string timestamp = logEntry.substr(0, space);
514 // Then get the log contents
515 size_t entryStart = logEntry.find_first_not_of(" ", space);
516 if (entryStart == std::string::npos)
517 {
518 return 1;
519 }
520 std::string_view entry(logEntry);
521 entry.remove_prefix(entryStart);
522 // Use split to separate the entry into its fields
523 std::vector<std::string> logEntryFields;
524 boost::split(logEntryFields, entry, boost::is_any_of(","),
525 boost::token_compress_on);
526 // We need at least a MessageId to be valid
527 if (logEntryFields.size() < 1)
528 {
529 return 1;
530 }
531 std::string &messageID = logEntryFields[0];
532 std::string &messageArgsStart = logEntryFields[1];
533 std::size_t messageArgsSize = logEntryFields.size() - 1;
Jason M. Bills95820182019-04-22 16:25:34 -0700534
Jason M. Bills4851d452019-03-28 11:27:48 -0700535 // Get the Message from the MessageRegistry
536 const message_registries::Message *message =
537 message_registries::getMessage(messageID);
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800538
Jason M. Bills4851d452019-03-28 11:27:48 -0700539 std::string msg;
540 std::string severity;
541 if (message != nullptr)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800542 {
Jason M. Bills4851d452019-03-28 11:27:48 -0700543 msg = message->message;
544 severity = message->severity;
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800545 }
546
Jason M. Bills95820182019-04-22 16:25:34 -0700547 // Get the MessageArgs from the log
548 boost::beast::span messageArgs(&messageArgsStart, messageArgsSize);
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800549
Jason M. Bills4851d452019-03-28 11:27:48 -0700550 // Fill the MessageArgs into the Message
Jason M. Bills95820182019-04-22 16:25:34 -0700551 int i = 0;
552 for (const std::string &messageArg : messageArgs)
Jason M. Bills4851d452019-03-28 11:27:48 -0700553 {
Jason M. Bills95820182019-04-22 16:25:34 -0700554 std::string argStr = "%" + std::to_string(++i);
Jason M. Bills4851d452019-03-28 11:27:48 -0700555 size_t argPos = msg.find(argStr);
556 if (argPos != std::string::npos)
557 {
Jason M. Bills95820182019-04-22 16:25:34 -0700558 msg.replace(argPos, argStr.length(), messageArg);
Jason M. Bills4851d452019-03-28 11:27:48 -0700559 }
560 }
561
Jason M. Bills95820182019-04-22 16:25:34 -0700562 // Get the Created time from the timestamp. The log timestamp is in RFC3339
563 // format which matches the Redfish format except for the fractional seconds
564 // between the '.' and the '+', so just remove them.
565 std::size_t dot = timestamp.find_first_of(".");
566 std::size_t plus = timestamp.find_first_of("+");
567 if (dot != std::string::npos && plus != std::string::npos)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800568 {
Jason M. Bills95820182019-04-22 16:25:34 -0700569 timestamp.erase(dot, plus - dot);
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800570 }
571
572 // Fill in the log entry with the gathered data
Jason M. Bills95820182019-04-22 16:25:34 -0700573 logEntryJson = {
Andrew Geisslercb92c032018-08-17 07:56:14 -0700574 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800575 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Ed Tanous029573d2019-02-01 10:57:49 -0800576 {"@odata.id",
Jason M. Bills95820182019-04-22 16:25:34 -0700577 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/#" +
578 logEntryID},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800579 {"Name", "System Event Log Entry"},
Jason M. Bills95820182019-04-22 16:25:34 -0700580 {"Id", logEntryID},
581 {"Message", std::move(msg)},
582 {"MessageId", std::move(messageID)},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800583 {"MessageArgs", std::move(messageArgs)},
584 {"EntryType", "Event"},
Jason M. Bills95820182019-04-22 16:25:34 -0700585 {"Severity", std::move(severity)},
586 {"Created", std::move(timestamp)}};
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800587 return 0;
588}
589
590class EventLogEntryCollection : public Node
591{
592 public:
593 template <typename CrowApp>
594 EventLogEntryCollection(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800595 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800596 {
597 entityPrivileges = {
598 {boost::beast::http::verb::get, {{"Login"}}},
599 {boost::beast::http::verb::head, {{"Login"}}},
600 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
601 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
602 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
603 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
604 }
605
606 private:
607 void doGet(crow::Response &res, const crow::Request &req,
608 const std::vector<std::string> &params) override
609 {
610 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
611 long skip = 0;
612 long top = maxEntriesPerPage; // Show max entries by default
613 if (!getSkipParam(asyncResp->res, req, skip))
614 {
615 return;
616 }
617 if (!getTopParam(asyncResp->res, req, top))
618 {
619 return;
620 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800621 // Collections don't include the static data added by SubRoute because
622 // it has a duplicate entry for members
623 asyncResp->res.jsonValue["@odata.type"] =
624 "#LogEntryCollection.LogEntryCollection";
625 asyncResp->res.jsonValue["@odata.context"] =
626 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
627 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800628 "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800629 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
630 asyncResp->res.jsonValue["Description"] =
631 "Collection of System Event Log Entries";
Andrew Geisslercb92c032018-08-17 07:56:14 -0700632
633#ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800634 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
635 logEntryArray = nlohmann::json::array();
Jason M. Bills95820182019-04-22 16:25:34 -0700636 // Go through the log files and create a unique ID for each entry
637 std::vector<std::filesystem::path> redfishLogFiles;
638 getRedfishLogFiles(redfishLogFiles);
Ed Tanousb01bf292019-03-25 19:25:26 +0000639 uint64_t entryCount = 0;
Jason M. Billscd225da2019-05-08 15:31:57 -0700640 std::string logEntry;
Jason M. Bills95820182019-04-22 16:25:34 -0700641
642 // Oldest logs are in the last file, so start there and loop backwards
Jason M. Billscd225da2019-05-08 15:31:57 -0700643 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend();
644 it++)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800645 {
Jason M. Billscd225da2019-05-08 15:31:57 -0700646 std::ifstream logStream(*it);
Jason M. Bills95820182019-04-22 16:25:34 -0700647 if (!logStream.is_open())
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800648 {
649 continue;
650 }
651
Jason M. Bills95820182019-04-22 16:25:34 -0700652 while (std::getline(logStream, logEntry))
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800653 {
Jason M. Bills95820182019-04-22 16:25:34 -0700654 entryCount++;
655 // Handle paging using skip (number of entries to skip from the
656 // start) and top (number of entries to display)
657 if (entryCount <= skip || entryCount > skip + top)
658 {
659 continue;
660 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800661
Jason M. Bills95820182019-04-22 16:25:34 -0700662 std::string idStr;
663 if (!getUniqueEntryID(logEntry, idStr))
664 {
665 continue;
666 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800667
Jason M. Bills95820182019-04-22 16:25:34 -0700668 logEntryArray.push_back({});
669 nlohmann::json &bmcLogEntry = logEntryArray.back();
670 if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0)
671 {
672 messages::internalError(asyncResp->res);
673 return;
674 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800675 }
676 }
677 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
678 if (skip + top < entryCount)
679 {
680 asyncResp->res.jsonValue["Members@odata.nextLink"] =
Jason M. Bills95820182019-04-22 16:25:34 -0700681 "/redfish/v1/Systems/system/LogServices/EventLog/"
682 "Entries?$skip=" +
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800683 std::to_string(skip + top);
684 }
Andrew Geisslercb92c032018-08-17 07:56:14 -0700685#else
686 // DBus implementation of EventLog/Entries
687 // Make call to Logging Service to find all log entry objects
688 crow::connections::systemBus->async_method_call(
689 [asyncResp](const boost::system::error_code ec,
690 GetManagedObjectsType &resp) {
691 if (ec)
692 {
693 // TODO Handle for specific error code
694 BMCWEB_LOG_ERROR
695 << "getLogEntriesIfaceData resp_handler got error "
696 << ec;
697 messages::internalError(asyncResp->res);
698 return;
699 }
700 nlohmann::json &entriesArray =
701 asyncResp->res.jsonValue["Members"];
702 entriesArray = nlohmann::json::array();
703 for (auto &objectPath : resp)
704 {
705 for (auto &interfaceMap : objectPath.second)
706 {
707 if (interfaceMap.first !=
708 "xyz.openbmc_project.Logging.Entry")
709 {
710 BMCWEB_LOG_DEBUG << "Bailing early on "
711 << interfaceMap.first;
712 continue;
713 }
714 entriesArray.push_back({});
715 nlohmann::json &thisEntry = entriesArray.back();
716 uint32_t *id;
717 std::time_t timestamp;
718 std::string *severity, *message;
719 bool *resolved;
720 for (auto &propertyMap : interfaceMap.second)
721 {
722 if (propertyMap.first == "Id")
723 {
724 id = sdbusplus::message::variant_ns::get_if<
725 uint32_t>(&propertyMap.second);
726 if (id == nullptr)
727 {
728 messages::propertyMissing(asyncResp->res,
729 "Id");
730 }
731 }
732 else if (propertyMap.first == "Timestamp")
733 {
734 const uint64_t *millisTimeStamp =
735 std::get_if<uint64_t>(&propertyMap.second);
736 if (millisTimeStamp == nullptr)
737 {
738 messages::propertyMissing(asyncResp->res,
739 "Timestamp");
740 }
741 // Retrieve Created property with format:
742 // yyyy-mm-ddThh:mm:ss
743 std::chrono::milliseconds chronoTimeStamp(
744 *millisTimeStamp);
745 timestamp =
746 std::chrono::duration_cast<
747 std::chrono::seconds>(chronoTimeStamp)
748 .count();
749 }
750 else if (propertyMap.first == "Severity")
751 {
752 severity = std::get_if<std::string>(
753 &propertyMap.second);
754 if (severity == nullptr)
755 {
756 messages::propertyMissing(asyncResp->res,
757 "Severity");
758 }
759 }
760 else if (propertyMap.first == "Message")
761 {
762 message = std::get_if<std::string>(
763 &propertyMap.second);
764 if (message == nullptr)
765 {
766 messages::propertyMissing(asyncResp->res,
767 "Message");
768 }
769 }
770 }
771 thisEntry = {
772 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
773 {"@odata.context", "/redfish/v1/"
774 "$metadata#LogEntry.LogEntry"},
775 {"@odata.id",
776 "/redfish/v1/Systems/system/LogServices/EventLog/"
777 "Entries/" +
778 std::to_string(*id)},
779 {"Name", "System DBus Event Log Entry"},
780 {"Id", std::to_string(*id)},
781 {"Message", *message},
782 {"EntryType", "Event"},
783 {"Severity",
784 translateSeverityDbusToRedfish(*severity)},
785 {"Created", crow::utility::getDateTime(timestamp)}};
786 }
787 }
788 std::sort(entriesArray.begin(), entriesArray.end(),
789 [](const nlohmann::json &left,
790 const nlohmann::json &right) {
791 return (left["Id"] <= right["Id"]);
792 });
793 asyncResp->res.jsonValue["Members@odata.count"] =
794 entriesArray.size();
795 },
796 "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging",
797 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
798#endif
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800799 }
800};
801
802class EventLogEntry : public Node
803{
804 public:
805 EventLogEntry(CrowApp &app) :
806 Node(app,
Ed Tanous029573d2019-02-01 10:57:49 -0800807 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/",
808 std::string())
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800809 {
810 entityPrivileges = {
811 {boost::beast::http::verb::get, {{"Login"}}},
812 {boost::beast::http::verb::head, {{"Login"}}},
813 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
814 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
815 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
816 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
817 }
818
819 private:
820 void doGet(crow::Response &res, const crow::Request &req,
821 const std::vector<std::string> &params) override
822 {
823 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous029573d2019-02-01 10:57:49 -0800824 if (params.size() != 1)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800825 {
826 messages::internalError(asyncResp->res);
827 return;
828 }
Ed Tanous029573d2019-02-01 10:57:49 -0800829 const std::string &entryID = params[0];
Andrew Geisslercb92c032018-08-17 07:56:14 -0700830
Jason M. Bills95820182019-04-22 16:25:34 -0700831#ifdef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
Andrew Geisslercb92c032018-08-17 07:56:14 -0700832 // DBus implementation of EventLog/Entries
833 // Make call to Logging Service to find all log entry objects
834 crow::connections::systemBus->async_method_call(
835 [asyncResp, entryID](const boost::system::error_code ec,
836 GetManagedPropertyType &resp) {
837 if (ec)
838 {
839 BMCWEB_LOG_ERROR
840 << "EventLogEntry (DBus) resp_handler got error " << ec;
841 messages::internalError(asyncResp->res);
842 return;
843 }
844 uint32_t *id;
845 std::time_t timestamp;
846 std::string *severity, *message;
847 bool *resolved;
848 for (auto &propertyMap : resp)
849 {
850 if (propertyMap.first == "Id")
851 {
852 id = std::get_if<uint32_t>(&propertyMap.second);
853 if (id == nullptr)
854 {
855 messages::propertyMissing(asyncResp->res, "Id");
856 }
857 }
858 else if (propertyMap.first == "Timestamp")
859 {
860 const uint64_t *millisTimeStamp =
861 std::get_if<uint64_t>(&propertyMap.second);
862 if (millisTimeStamp == nullptr)
863 {
864 messages::propertyMissing(asyncResp->res,
865 "Timestamp");
866 }
867 // Retrieve Created property with format:
868 // yyyy-mm-ddThh:mm:ss
869 std::chrono::milliseconds chronoTimeStamp(
870 *millisTimeStamp);
871 timestamp =
872 std::chrono::duration_cast<std::chrono::seconds>(
873 chronoTimeStamp)
874 .count();
875 }
876 else if (propertyMap.first == "Severity")
877 {
878 severity =
879 std::get_if<std::string>(&propertyMap.second);
880 if (severity == nullptr)
881 {
882 messages::propertyMissing(asyncResp->res,
883 "Severity");
884 }
885 }
886 else if (propertyMap.first == "Message")
887 {
888 message = std::get_if<std::string>(&propertyMap.second);
889 if (message == nullptr)
890 {
891 messages::propertyMissing(asyncResp->res,
892 "Message");
893 }
894 }
895 }
896 asyncResp->res.jsonValue = {
897 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
898 {"@odata.context", "/redfish/v1/"
899 "$metadata#LogEntry.LogEntry"},
900 {"@odata.id",
901 "/redfish/v1/Systems/system/LogServices/EventLog/"
902 "Entries/" +
903 std::to_string(*id)},
904 {"Name", "System DBus Event Log Entry"},
905 {"Id", std::to_string(*id)},
906 {"Message", *message},
907 {"EntryType", "Event"},
908 {"Severity", translateSeverityDbusToRedfish(*severity)},
Jason M. Bills95820182019-04-22 16:25:34 -0700909 { "Created",
910 crow::utility::getDateTime(timestamp) }};
Andrew Geisslercb92c032018-08-17 07:56:14 -0700911 },
912 "xyz.openbmc_project.Logging",
913 "/xyz/openbmc_project/logging/entry/" + entryID,
914 "org.freedesktop.DBus.Properties", "GetAll",
915 "xyz.openbmc_project.Logging.Entry");
916#endif
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800917 }
918};
919
920class BMCLogServiceCollection : public Node
921{
922 public:
923 template <typename CrowApp>
924 BMCLogServiceCollection(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700925 Node(app, "/redfish/v1/Managers/bmc/LogServices/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700926 {
Ed Tanous1da66f72018-07-27 16:13:37 -0700927 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700928 {boost::beast::http::verb::get, {{"Login"}}},
929 {boost::beast::http::verb::head, {{"Login"}}},
930 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
931 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
932 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
933 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700934 }
935
936 private:
937 /**
938 * Functions triggers appropriate requests on DBus
939 */
940 void doGet(crow::Response &res, const crow::Request &req,
941 const std::vector<std::string> &params) override
942 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700943 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700944 // Collections don't include the static data added by SubRoute because
945 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -0700946 asyncResp->res.jsonValue["@odata.type"] =
Ed Tanous1da66f72018-07-27 16:13:37 -0700947 "#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700948 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800949 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700950 asyncResp->res.jsonValue["@odata.id"] =
951 "/redfish/v1/Managers/bmc/LogServices";
952 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
953 asyncResp->res.jsonValue["Description"] =
Ed Tanous1da66f72018-07-27 16:13:37 -0700954 "Collection of LogServices for this Manager";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800955 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
956 logServiceArray = nlohmann::json::array();
957#ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
958 logServiceArray.push_back(
Andrew Geisslercb92c032018-08-17 07:56:14 -0700959 {{ "@odata.id",
960 "/redfish/v1/Managers/bmc/LogServices/Journal" }});
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800961#endif
Jason M. Billse1f26342018-07-18 12:12:00 -0700962 asyncResp->res.jsonValue["Members@odata.count"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800963 logServiceArray.size();
Ed Tanous1da66f72018-07-27 16:13:37 -0700964 }
965};
966
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800967class BMCJournalLogService : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700968{
969 public:
970 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800971 BMCJournalLogService(CrowApp &app) :
972 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
Jason M. Billse1f26342018-07-18 12:12:00 -0700973 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700974 entityPrivileges = {
975 {boost::beast::http::verb::get, {{"Login"}}},
976 {boost::beast::http::verb::head, {{"Login"}}},
977 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
978 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
979 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
980 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
981 }
982
983 private:
984 void doGet(crow::Response &res, const crow::Request &req,
985 const std::vector<std::string> &params) override
986 {
987 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700988 asyncResp->res.jsonValue["@odata.type"] =
989 "#LogService.v1_1_0.LogService";
Ed Tanous0f74e642018-11-12 15:17:05 -0800990 asyncResp->res.jsonValue["@odata.id"] =
991 "/redfish/v1/Managers/bmc/LogServices/Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -0700992 asyncResp->res.jsonValue["@odata.context"] =
993 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800994 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
995 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
996 asyncResp->res.jsonValue["Id"] = "BMC Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -0700997 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
Jason M. Billscd50aa42019-02-12 17:09:02 -0800998 asyncResp->res.jsonValue["Entries"] = {
999 {"@odata.id",
Ed Tanous086be232019-05-23 11:47:09 -07001000 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}};
Jason M. Billse1f26342018-07-18 12:12:00 -07001001 }
1002};
1003
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001004static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID,
1005 sd_journal *journal,
1006 nlohmann::json &bmcJournalLogEntryJson)
Jason M. Billse1f26342018-07-18 12:12:00 -07001007{
1008 // Get the Log Entry contents
1009 int ret = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -07001010
Ed Tanous39e77502019-03-04 17:35:53 -08001011 std::string_view msg;
Jason M. Bills16428a12018-11-02 12:42:29 -07001012 ret = getJournalMetadata(journal, "MESSAGE", msg);
Jason M. Billse1f26342018-07-18 12:12:00 -07001013 if (ret < 0)
1014 {
1015 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
1016 return 1;
1017 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001018
1019 // Get the severity from the PRIORITY field
Jason M. Billse1f26342018-07-18 12:12:00 -07001020 int severity = 8; // Default to an invalid priority
Jason M. Bills16428a12018-11-02 12:42:29 -07001021 ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
Jason M. Billse1f26342018-07-18 12:12:00 -07001022 if (ret < 0)
1023 {
1024 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
1025 return 1;
1026 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001027
1028 // Get the Created time from the timestamp
Jason M. Bills16428a12018-11-02 12:42:29 -07001029 std::string entryTimeStr;
1030 if (!getEntryTimestamp(journal, entryTimeStr))
Jason M. Billse1f26342018-07-18 12:12:00 -07001031 {
Jason M. Bills16428a12018-11-02 12:42:29 -07001032 return 1;
Jason M. Billse1f26342018-07-18 12:12:00 -07001033 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001034
1035 // Fill in the log entry with the gathered data
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001036 bmcJournalLogEntryJson = {
Andrew Geisslercb92c032018-08-17 07:56:14 -07001037 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
Jason M. Billse1f26342018-07-18 12:12:00 -07001038 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001039 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
1040 bmcJournalLogEntryID},
Jason M. Billse1f26342018-07-18 12:12:00 -07001041 {"Name", "BMC Journal Entry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001042 {"Id", bmcJournalLogEntryID},
Jason M. Bills16428a12018-11-02 12:42:29 -07001043 {"Message", msg},
Jason M. Billse1f26342018-07-18 12:12:00 -07001044 {"EntryType", "Oem"},
1045 {"Severity",
1046 severity <= 2 ? "Critical"
1047 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
Ed Tanous086be232019-05-23 11:47:09 -07001048 {"OemRecordFormat", "BMC Journal Entry"},
Jason M. Billse1f26342018-07-18 12:12:00 -07001049 {"Created", std::move(entryTimeStr)}};
1050 return 0;
1051}
1052
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001053class BMCJournalLogEntryCollection : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -07001054{
1055 public:
1056 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001057 BMCJournalLogEntryCollection(CrowApp &app) :
1058 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
Jason M. Billse1f26342018-07-18 12:12:00 -07001059 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001060 entityPrivileges = {
1061 {boost::beast::http::verb::get, {{"Login"}}},
1062 {boost::beast::http::verb::head, {{"Login"}}},
1063 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1064 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1065 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1066 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1067 }
1068
1069 private:
1070 void doGet(crow::Response &res, const crow::Request &req,
1071 const std::vector<std::string> &params) override
1072 {
1073 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001074 static constexpr const long maxEntriesPerPage = 1000;
1075 long skip = 0;
1076 long top = maxEntriesPerPage; // Show max entries by default
Jason M. Bills16428a12018-11-02 12:42:29 -07001077 if (!getSkipParam(asyncResp->res, req, skip))
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001078 {
Jason M. Bills16428a12018-11-02 12:42:29 -07001079 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001080 }
Jason M. Bills16428a12018-11-02 12:42:29 -07001081 if (!getTopParam(asyncResp->res, req, top))
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001082 {
Jason M. Bills16428a12018-11-02 12:42:29 -07001083 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001084 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001085 // Collections don't include the static data added by SubRoute because
1086 // it has a duplicate entry for members
1087 asyncResp->res.jsonValue["@odata.type"] =
1088 "#LogEntryCollection.LogEntryCollection";
Ed Tanous0f74e642018-11-12 15:17:05 -08001089 asyncResp->res.jsonValue["@odata.id"] =
1090 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001091 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001092 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -07001093 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001094 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001095 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
1096 asyncResp->res.jsonValue["Description"] =
1097 "Collection of BMC Journal Entries";
Ed Tanous0f74e642018-11-12 15:17:05 -08001098 asyncResp->res.jsonValue["@odata.id"] =
1099 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001100 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1101 logEntryArray = nlohmann::json::array();
1102
1103 // Go through the journal and use the timestamp to create a unique ID
1104 // for each entry
1105 sd_journal *journalTmp = nullptr;
1106 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1107 if (ret < 0)
1108 {
1109 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -07001110 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001111 return;
1112 }
1113 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1114 journalTmp, sd_journal_close);
1115 journalTmp = nullptr;
Ed Tanousb01bf292019-03-25 19:25:26 +00001116 uint64_t entryCount = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -07001117 SD_JOURNAL_FOREACH(journal.get())
1118 {
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001119 entryCount++;
1120 // Handle paging using skip (number of entries to skip from the
1121 // start) and top (number of entries to display)
1122 if (entryCount <= skip || entryCount > skip + top)
1123 {
1124 continue;
1125 }
1126
Jason M. Bills16428a12018-11-02 12:42:29 -07001127 std::string idStr;
1128 if (!getUniqueEntryID(journal.get(), idStr))
Jason M. Billse1f26342018-07-18 12:12:00 -07001129 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001130 continue;
1131 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001132
Jason M. Billse1f26342018-07-18 12:12:00 -07001133 logEntryArray.push_back({});
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001134 nlohmann::json &bmcJournalLogEntry = logEntryArray.back();
1135 if (fillBMCJournalLogEntryJson(idStr, journal.get(),
1136 bmcJournalLogEntry) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -07001137 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001138 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001139 return;
1140 }
1141 }
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001142 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
1143 if (skip + top < entryCount)
1144 {
1145 asyncResp->res.jsonValue["Members@odata.nextLink"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001146 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001147 std::to_string(skip + top);
1148 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001149 }
1150};
1151
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001152class BMCJournalLogEntry : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -07001153{
1154 public:
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001155 BMCJournalLogEntry(CrowApp &app) :
1156 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/",
Jason M. Billse1f26342018-07-18 12:12:00 -07001157 std::string())
1158 {
1159 entityPrivileges = {
1160 {boost::beast::http::verb::get, {{"Login"}}},
1161 {boost::beast::http::verb::head, {{"Login"}}},
1162 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1163 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1164 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1165 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1166 }
1167
1168 private:
1169 void doGet(crow::Response &res, const crow::Request &req,
1170 const std::vector<std::string> &params) override
1171 {
1172 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1173 if (params.size() != 1)
1174 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001175 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001176 return;
1177 }
Jason M. Bills16428a12018-11-02 12:42:29 -07001178 const std::string &entryID = params[0];
Jason M. Billse1f26342018-07-18 12:12:00 -07001179 // Convert the unique ID back to a timestamp to find the entry
Jason M. Billse1f26342018-07-18 12:12:00 -07001180 uint64_t ts = 0;
1181 uint16_t index = 0;
Jason M. Bills16428a12018-11-02 12:42:29 -07001182 if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
Jason M. Billse1f26342018-07-18 12:12:00 -07001183 {
Jason M. Bills16428a12018-11-02 12:42:29 -07001184 return;
Jason M. Billse1f26342018-07-18 12:12:00 -07001185 }
1186
1187 sd_journal *journalTmp = nullptr;
1188 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1189 if (ret < 0)
1190 {
1191 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -07001192 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001193 return;
1194 }
1195 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1196 journalTmp, sd_journal_close);
1197 journalTmp = nullptr;
1198 // Go to the timestamp in the log and move to the entry at the index
1199 ret = sd_journal_seek_realtime_usec(journal.get(), ts);
1200 for (int i = 0; i <= index; i++)
1201 {
1202 sd_journal_next(journal.get());
1203 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001204 // Confirm that the entry ID matches what was requested
1205 std::string idStr;
1206 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
1207 {
1208 messages::resourceMissingAtURI(asyncResp->res, entryID);
1209 return;
1210 }
1211
1212 if (fillBMCJournalLogEntryJson(entryID, journal.get(),
1213 asyncResp->res.jsonValue) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -07001214 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001215 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001216 return;
1217 }
1218 }
1219};
1220
Jason M. Bills424c4172019-03-21 13:50:33 -07001221class CrashdumpService : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -07001222{
1223 public:
1224 template <typename CrowApp>
Jason M. Bills424c4172019-03-21 13:50:33 -07001225 CrashdumpService(CrowApp &app) :
1226 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001227 {
Ed Tanous1da66f72018-07-27 16:13:37 -07001228 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001229 {boost::beast::http::verb::get, {{"Login"}}},
1230 {boost::beast::http::verb::head, {{"Login"}}},
1231 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1232 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1233 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1234 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001235 }
1236
1237 private:
1238 /**
1239 * Functions triggers appropriate requests on DBus
1240 */
1241 void doGet(crow::Response &res, const crow::Request &req,
1242 const std::vector<std::string> &params) override
1243 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001244 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001245 // Copy over the static data to include the entries added by SubRoute
Ed Tanous0f74e642018-11-12 15:17:05 -08001246 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Bills424c4172019-03-21 13:50:33 -07001247 "/redfish/v1/Systems/system/LogServices/Crashdump";
Jason M. Billse1f26342018-07-18 12:12:00 -07001248 asyncResp->res.jsonValue["@odata.type"] =
1249 "#LogService.v1_1_0.LogService";
1250 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001251 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Bills424c4172019-03-21 13:50:33 -07001252 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Service";
1253 asyncResp->res.jsonValue["Description"] = "Crashdump Service";
1254 asyncResp->res.jsonValue["Id"] = "Crashdump";
Jason M. Billse1f26342018-07-18 12:12:00 -07001255 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
1256 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
Jason M. Billscd50aa42019-02-12 17:09:02 -08001257 asyncResp->res.jsonValue["Entries"] = {
1258 {"@odata.id",
Jason M. Bills424c4172019-03-21 13:50:33 -07001259 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}};
Jason M. Billse1f26342018-07-18 12:12:00 -07001260 asyncResp->res.jsonValue["Actions"] = {
Ed Tanous1da66f72018-07-27 16:13:37 -07001261 {"Oem",
Jason M. Bills424c4172019-03-21 13:50:33 -07001262 {{"#Crashdump.OnDemand",
1263 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1264 "Actions/Oem/Crashdump.OnDemand"}}}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001265
1266#ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
Jason M. Billse1f26342018-07-18 12:12:00 -07001267 asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
Jason M. Bills424c4172019-03-21 13:50:33 -07001268 {"#Crashdump.SendRawPeci",
Andrew Geisslercb92c032018-08-17 07:56:14 -07001269 { { "target",
Jason M. Bills424c4172019-03-21 13:50:33 -07001270 "/redfish/v1/Systems/system/LogServices/Crashdump/"
1271 "Actions/Oem/Crashdump.SendRawPeci" } }});
Ed Tanous1da66f72018-07-27 16:13:37 -07001272#endif
Ed Tanous1da66f72018-07-27 16:13:37 -07001273 }
1274};
1275
Jason M. Bills424c4172019-03-21 13:50:33 -07001276class CrashdumpEntryCollection : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001277{
1278 public:
1279 template <typename CrowApp>
Jason M. Bills424c4172019-03-21 13:50:33 -07001280 CrashdumpEntryCollection(CrowApp &app) :
1281 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001282 {
Ed Tanous1da66f72018-07-27 16:13:37 -07001283 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001284 {boost::beast::http::verb::get, {{"Login"}}},
1285 {boost::beast::http::verb::head, {{"Login"}}},
1286 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1287 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1288 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1289 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001290 }
1291
1292 private:
1293 /**
1294 * Functions triggers appropriate requests on DBus
1295 */
1296 void doGet(crow::Response &res, const crow::Request &req,
1297 const std::vector<std::string> &params) override
1298 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001299 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001300 // Collections don't include the static data added by SubRoute because
1301 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -07001302 auto getLogEntriesCallback = [asyncResp](
1303 const boost::system::error_code ec,
1304 const std::vector<std::string> &resp) {
1305 if (ec)
1306 {
1307 if (ec.value() !=
1308 boost::system::errc::no_such_file_or_directory)
Ed Tanous1da66f72018-07-27 16:13:37 -07001309 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001310 BMCWEB_LOG_DEBUG << "failed to get entries ec: "
1311 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001312 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001313 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07001314 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001315 }
1316 asyncResp->res.jsonValue["@odata.type"] =
1317 "#LogEntryCollection.LogEntryCollection";
Ed Tanous0f74e642018-11-12 15:17:05 -08001318 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Bills424c4172019-03-21 13:50:33 -07001319 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001320 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsd53dd412019-02-12 17:16:22 -08001321 "/redfish/v1/"
1322 "$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Bills424c4172019-03-21 13:50:33 -07001323 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001324 asyncResp->res.jsonValue["Description"] =
Jason M. Bills424c4172019-03-21 13:50:33 -07001325 "Collection of Crashdump Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001326 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1327 logEntryArray = nlohmann::json::array();
1328 for (const std::string &objpath : resp)
1329 {
Jason M. Bills48e46392019-02-13 12:58:37 -08001330 // Don't list the on-demand log
Jason M. Bills424c4172019-03-21 13:50:33 -07001331 if (objpath.compare(CrashdumpOnDemandPath) == 0)
Ed Tanous1da66f72018-07-27 16:13:37 -07001332 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001333 continue;
Ed Tanous1da66f72018-07-27 16:13:37 -07001334 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001335 std::size_t lastPos = objpath.rfind("/");
1336 if (lastPos != std::string::npos)
1337 {
1338 logEntryArray.push_back(
Jason M. Billsd53dd412019-02-12 17:16:22 -08001339 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/"
Jason M. Bills424c4172019-03-21 13:50:33 -07001340 "Crashdump/Entries/" +
Jason M. Billse1f26342018-07-18 12:12:00 -07001341 objpath.substr(lastPos + 1)}});
1342 }
1343 }
1344 asyncResp->res.jsonValue["Members@odata.count"] =
1345 logEntryArray.size();
1346 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001347 crow::connections::systemBus->async_method_call(
1348 std::move(getLogEntriesCallback),
1349 "xyz.openbmc_project.ObjectMapper",
1350 "/xyz/openbmc_project/object_mapper",
1351 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
Jason M. Bills424c4172019-03-21 13:50:33 -07001352 std::array<const char *, 1>{CrashdumpInterface});
Ed Tanous1da66f72018-07-27 16:13:37 -07001353 }
1354};
1355
Jason M. Bills424c4172019-03-21 13:50:33 -07001356std::string getLogCreatedTime(const nlohmann::json &Crashdump)
Ed Tanous1da66f72018-07-27 16:13:37 -07001357{
Jason M. Bills424c4172019-03-21 13:50:33 -07001358 nlohmann::json::const_iterator cdIt = Crashdump.find("crashlog_data");
1359 if (cdIt != Crashdump.end())
Ed Tanous1da66f72018-07-27 16:13:37 -07001360 {
Jason M. Billsc4d00432019-02-12 17:17:48 -08001361 nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO");
1362 if (siIt != cdIt->end())
Ed Tanous1da66f72018-07-27 16:13:37 -07001363 {
Jason M. Billsc4d00432019-02-12 17:17:48 -08001364 nlohmann::json::const_iterator tsIt = siIt->find("timestamp");
1365 if (tsIt != siIt->end())
Ed Tanous1da66f72018-07-27 16:13:37 -07001366 {
Jason M. Billsc4d00432019-02-12 17:17:48 -08001367 const std::string *logTime =
1368 tsIt->get_ptr<const std::string *>();
1369 if (logTime != nullptr)
1370 {
1371 return *logTime;
1372 }
Ed Tanous1da66f72018-07-27 16:13:37 -07001373 }
1374 }
1375 }
1376 BMCWEB_LOG_DEBUG << "failed to find log timestamp";
1377
1378 return std::string();
1379}
1380
Jason M. Bills424c4172019-03-21 13:50:33 -07001381class CrashdumpEntry : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001382{
1383 public:
Jason M. Bills424c4172019-03-21 13:50:33 -07001384 CrashdumpEntry(CrowApp &app) :
Jason M. Billsd53dd412019-02-12 17:16:22 -08001385 Node(app,
Jason M. Bills424c4172019-03-21 13:50:33 -07001386 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/",
Ed Tanous1da66f72018-07-27 16:13:37 -07001387 std::string())
1388 {
1389 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001390 {boost::beast::http::verb::get, {{"Login"}}},
1391 {boost::beast::http::verb::head, {{"Login"}}},
1392 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1393 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1394 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1395 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001396 }
1397
1398 private:
1399 void doGet(crow::Response &res, const crow::Request &req,
1400 const std::vector<std::string> &params) override
1401 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001402 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001403 if (params.size() != 1)
1404 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001405 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001406 return;
1407 }
Ed Tanousb01bf292019-03-25 19:25:26 +00001408 const uint8_t logId = std::atoi(params[0].c_str());
Ed Tanousabf2add2019-01-22 16:40:12 -08001409 auto getStoredLogCallback = [asyncResp, logId](
1410 const boost::system::error_code ec,
1411 const std::variant<std::string> &resp) {
1412 if (ec)
1413 {
1414 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
1415 messages::internalError(asyncResp->res);
1416 return;
1417 }
1418 const std::string *log = std::get_if<std::string>(&resp);
1419 if (log == nullptr)
1420 {
1421 messages::internalError(asyncResp->res);
1422 return;
1423 }
1424 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1425 if (j.is_discarded())
1426 {
1427 messages::internalError(asyncResp->res);
1428 return;
1429 }
1430 std::string t = getLogCreatedTime(j);
1431 asyncResp->res.jsonValue = {
Andrew Geisslercb92c032018-08-17 07:56:14 -07001432 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
Ed Tanousabf2add2019-01-22 16:40:12 -08001433 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1434 {"@odata.id",
Jason M. Bills424c4172019-03-21 13:50:33 -07001435 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
Ed Tanousabf2add2019-01-22 16:40:12 -08001436 std::to_string(logId)},
Jason M. Bills424c4172019-03-21 13:50:33 -07001437 {"Name", "CPU Crashdump"},
Ed Tanousabf2add2019-01-22 16:40:12 -08001438 {"Id", logId},
1439 {"EntryType", "Oem"},
Jason M. Bills424c4172019-03-21 13:50:33 -07001440 {"OemRecordFormat", "Intel Crashdump"},
Ed Tanousabf2add2019-01-22 16:40:12 -08001441 {"Oem", {{"Intel", std::move(j)}}},
1442 {"Created", std::move(t)}};
1443 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001444 crow::connections::systemBus->async_method_call(
Jason M. Bills424c4172019-03-21 13:50:33 -07001445 std::move(getStoredLogCallback), CrashdumpObject,
1446 CrashdumpPath + std::string("/") + std::to_string(logId),
1447 "org.freedesktop.DBus.Properties", "Get", CrashdumpInterface,
1448 "Log");
Ed Tanous1da66f72018-07-27 16:13:37 -07001449 }
1450};
1451
Jason M. Bills424c4172019-03-21 13:50:33 -07001452class OnDemandCrashdump : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001453{
1454 public:
Jason M. Bills424c4172019-03-21 13:50:33 -07001455 OnDemandCrashdump(CrowApp &app) :
1456 Node(app,
1457 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
1458 "Crashdump.OnDemand/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001459 {
1460 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001461 {boost::beast::http::verb::get, {{"Login"}}},
1462 {boost::beast::http::verb::head, {{"Login"}}},
1463 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1464 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1465 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1466 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001467 }
1468
1469 private:
1470 void doPost(crow::Response &res, const crow::Request &req,
1471 const std::vector<std::string> &params) override
1472 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001473 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Bills48e46392019-02-13 12:58:37 -08001474 static std::unique_ptr<sdbusplus::bus::match::match> onDemandLogMatcher;
Ed Tanous1da66f72018-07-27 16:13:37 -07001475
Jason M. Bills48e46392019-02-13 12:58:37 -08001476 // Only allow one OnDemand Log request at a time
1477 if (onDemandLogMatcher != nullptr)
Ed Tanous1da66f72018-07-27 16:13:37 -07001478 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001479 asyncResp->res.addHeader("Retry-After", "30");
Jason M. Billsf12894f2018-10-09 12:45:45 -07001480 messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
Ed Tanous1da66f72018-07-27 16:13:37 -07001481 return;
1482 }
1483 // Make this static so it survives outside this method
1484 static boost::asio::deadline_timer timeout(*req.ioService);
1485
1486 timeout.expires_from_now(boost::posix_time::seconds(30));
Jason M. Billse1f26342018-07-18 12:12:00 -07001487 timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
Jason M. Bills48e46392019-02-13 12:58:37 -08001488 onDemandLogMatcher = nullptr;
Ed Tanous1da66f72018-07-27 16:13:37 -07001489 if (ec)
1490 {
1491 // operation_aborted is expected if timer is canceled before
1492 // completion.
1493 if (ec != boost::asio::error::operation_aborted)
1494 {
1495 BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
1496 }
1497 return;
1498 }
Jason M. Bills48e46392019-02-13 12:58:37 -08001499 BMCWEB_LOG_ERROR << "Timed out waiting for on-demand log";
Ed Tanous1da66f72018-07-27 16:13:37 -07001500
Jason M. Billsf12894f2018-10-09 12:45:45 -07001501 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001502 });
1503
Jason M. Bills48e46392019-02-13 12:58:37 -08001504 auto onDemandLogMatcherCallback = [asyncResp](
1505 sdbusplus::message::message &m) {
1506 BMCWEB_LOG_DEBUG << "OnDemand log available match fired";
Ed Tanous1da66f72018-07-27 16:13:37 -07001507 boost::system::error_code ec;
1508 timeout.cancel(ec);
1509 if (ec)
1510 {
1511 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
1512 }
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001513 sdbusplus::message::object_path objPath;
Ed Tanous1da66f72018-07-27 16:13:37 -07001514 boost::container::flat_map<
Ed Tanousabf2add2019-01-22 16:40:12 -08001515 std::string, boost::container::flat_map<
1516 std::string, std::variant<std::string>>>
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001517 interfacesAdded;
1518 m.read(objPath, interfacesAdded);
Ed Tanousabf2add2019-01-22 16:40:12 -08001519 const std::string *log = std::get_if<std::string>(
Jason M. Bills424c4172019-03-21 13:50:33 -07001520 &interfacesAdded[CrashdumpInterface]["Log"]);
Ed Tanous1da66f72018-07-27 16:13:37 -07001521 if (log == nullptr)
1522 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001523 messages::internalError(asyncResp->res);
Jason M. Bills48e46392019-02-13 12:58:37 -08001524 // Careful with onDemandLogMatcher. It is a unique_ptr to the
Ed Tanous1da66f72018-07-27 16:13:37 -07001525 // match object inside which this lambda is executing. Once it
1526 // is set to nullptr, the match object will be destroyed and the
1527 // lambda will lose its context, including res, so it needs to
1528 // be the last thing done.
Jason M. Bills48e46392019-02-13 12:58:37 -08001529 onDemandLogMatcher = nullptr;
Ed Tanous1da66f72018-07-27 16:13:37 -07001530 return;
1531 }
1532 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1533 if (j.is_discarded())
1534 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001535 messages::internalError(asyncResp->res);
Jason M. Bills48e46392019-02-13 12:58:37 -08001536 // Careful with onDemandLogMatcher. It is a unique_ptr to the
Ed Tanous1da66f72018-07-27 16:13:37 -07001537 // match object inside which this lambda is executing. Once it
1538 // is set to nullptr, the match object will be destroyed and the
1539 // lambda will lose its context, including res, so it needs to
1540 // be the last thing done.
Jason M. Bills48e46392019-02-13 12:58:37 -08001541 onDemandLogMatcher = nullptr;
Ed Tanous1da66f72018-07-27 16:13:37 -07001542 return;
1543 }
1544 std::string t = getLogCreatedTime(j);
Jason M. Billse1f26342018-07-18 12:12:00 -07001545 asyncResp->res.jsonValue = {
Andrew Geisslercb92c032018-08-17 07:56:14 -07001546 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
Ed Tanous1da66f72018-07-27 16:13:37 -07001547 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Jason M. Bills424c4172019-03-21 13:50:33 -07001548 {"Name", "CPU Crashdump"},
Ed Tanous1da66f72018-07-27 16:13:37 -07001549 {"EntryType", "Oem"},
Jason M. Bills424c4172019-03-21 13:50:33 -07001550 {"OemRecordFormat", "Intel Crashdump"},
Ed Tanous1da66f72018-07-27 16:13:37 -07001551 {"Oem", {{"Intel", std::move(j)}}},
1552 {"Created", std::move(t)}};
Jason M. Bills48e46392019-02-13 12:58:37 -08001553 // Careful with onDemandLogMatcher. It is a unique_ptr to the
Ed Tanous1da66f72018-07-27 16:13:37 -07001554 // match object inside which this lambda is executing. Once it is
1555 // set to nullptr, the match object will be destroyed and the lambda
1556 // will lose its context, including res, so it needs to be the last
1557 // thing done.
Jason M. Bills48e46392019-02-13 12:58:37 -08001558 onDemandLogMatcher = nullptr;
Ed Tanous1da66f72018-07-27 16:13:37 -07001559 };
Jason M. Bills48e46392019-02-13 12:58:37 -08001560 onDemandLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
Ed Tanous1da66f72018-07-27 16:13:37 -07001561 *crow::connections::systemBus,
1562 sdbusplus::bus::match::rules::interfacesAdded() +
Jason M. Bills424c4172019-03-21 13:50:33 -07001563 sdbusplus::bus::match::rules::argNpath(0,
1564 CrashdumpOnDemandPath),
Jason M. Bills48e46392019-02-13 12:58:37 -08001565 std::move(onDemandLogMatcherCallback));
Ed Tanous1da66f72018-07-27 16:13:37 -07001566
Jason M. Bills48e46392019-02-13 12:58:37 -08001567 auto generateonDemandLogCallback =
Jason M. Billse1f26342018-07-18 12:12:00 -07001568 [asyncResp](const boost::system::error_code ec,
1569 const std::string &resp) {
Ed Tanous1da66f72018-07-27 16:13:37 -07001570 if (ec)
1571 {
1572 if (ec.value() ==
1573 boost::system::errc::operation_not_supported)
1574 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001575 messages::resourceInStandby(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001576 }
1577 else
1578 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001579 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001580 }
Ed Tanous1da66f72018-07-27 16:13:37 -07001581 boost::system::error_code timeoutec;
1582 timeout.cancel(timeoutec);
1583 if (timeoutec)
1584 {
1585 BMCWEB_LOG_ERROR << "error canceling timer "
1586 << timeoutec;
1587 }
Jason M. Bills48e46392019-02-13 12:58:37 -08001588 onDemandLogMatcher = nullptr;
Ed Tanous1da66f72018-07-27 16:13:37 -07001589 return;
1590 }
1591 };
1592 crow::connections::systemBus->async_method_call(
Jason M. Bills424c4172019-03-21 13:50:33 -07001593 std::move(generateonDemandLogCallback), CrashdumpObject,
1594 CrashdumpPath, CrashdumpOnDemandInterface, "GenerateOnDemandLog");
Ed Tanous1da66f72018-07-27 16:13:37 -07001595 }
1596};
1597
Jason M. Billse1f26342018-07-18 12:12:00 -07001598class SendRawPECI : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001599{
1600 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001601 SendRawPECI(CrowApp &app) :
Jason M. Bills424c4172019-03-21 13:50:33 -07001602 Node(app,
1603 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
1604 "Crashdump.SendRawPeci/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001605 {
1606 entityPrivileges = {
1607 {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
1608 {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
1609 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1610 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1611 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1612 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1613 }
1614
1615 private:
1616 void doPost(crow::Response &res, const crow::Request &req,
1617 const std::vector<std::string> &params) override
1618 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001619 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanousb1556422018-10-16 14:09:17 -07001620 uint8_t clientAddress = 0;
1621 uint8_t readLength = 0;
Ed Tanous1da66f72018-07-27 16:13:37 -07001622 std::vector<uint8_t> peciCommand;
Ed Tanousb1556422018-10-16 14:09:17 -07001623 if (!json_util::readJson(req, res, "ClientAddress", clientAddress,
1624 "ReadLength", readLength, "PECICommand",
1625 peciCommand))
Ed Tanous1da66f72018-07-27 16:13:37 -07001626 {
Ed Tanousb1556422018-10-16 14:09:17 -07001627 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07001628 }
Ed Tanousb1556422018-10-16 14:09:17 -07001629
Ed Tanous1da66f72018-07-27 16:13:37 -07001630 // Callback to return the Raw PECI response
Jason M. Billse1f26342018-07-18 12:12:00 -07001631 auto sendRawPECICallback =
1632 [asyncResp](const boost::system::error_code ec,
1633 const std::vector<uint8_t> &resp) {
1634 if (ec)
1635 {
1636 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
1637 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001638 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001639 return;
1640 }
1641 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
1642 {"PECIResponse", resp}};
1643 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001644 // Call the SendRawPECI command with the provided data
1645 crow::connections::systemBus->async_method_call(
Jason M. Bills424c4172019-03-21 13:50:33 -07001646 std::move(sendRawPECICallback), CrashdumpObject, CrashdumpPath,
1647 CrashdumpRawPECIInterface, "SendRawPeci", clientAddress, readLength,
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001648 peciCommand);
Ed Tanous1da66f72018-07-27 16:13:37 -07001649 }
1650};
1651
Andrew Geisslercb92c032018-08-17 07:56:14 -07001652/**
1653 * DBusLogServiceActionsClear class supports POST method for ClearLog action.
1654 */
1655class DBusLogServiceActionsClear : public Node
1656{
1657 public:
1658 DBusLogServiceActionsClear(CrowApp &app) :
1659 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
1660 "LogService.Reset")
1661 {
1662 entityPrivileges = {
1663 {boost::beast::http::verb::get, {{"Login"}}},
1664 {boost::beast::http::verb::head, {{"Login"}}},
1665 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1666 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1667 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1668 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1669 }
1670
1671 private:
1672 /**
1673 * Function handles POST method request.
1674 * The Clear Log actions does not require any parameter.The action deletes
1675 * all entries found in the Entries collection for this Log Service.
1676 */
1677 void doPost(crow::Response &res, const crow::Request &req,
1678 const std::vector<std::string> &params) override
1679 {
1680 BMCWEB_LOG_DEBUG << "Do delete all entries.";
1681
1682 auto asyncResp = std::make_shared<AsyncResp>(res);
1683 // Process response from Logging service.
1684 auto resp_handler = [asyncResp](const boost::system::error_code ec) {
1685 BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done";
1686 if (ec)
1687 {
1688 // TODO Handle for specific error code
1689 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec;
1690 asyncResp->res.result(
1691 boost::beast::http::status::internal_server_error);
1692 return;
1693 }
1694
1695 asyncResp->res.result(boost::beast::http::status::no_content);
1696 };
1697
1698 // Make call to Logging service to request Clear Log
1699 crow::connections::systemBus->async_method_call(
1700 resp_handler, "xyz.openbmc_project.Logging",
1701 "/xyz/openbmc_project/logging",
1702 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
1703 }
1704};
Ed Tanous1da66f72018-07-27 16:13:37 -07001705} // namespace redfish