blob: c5eae83a70f106debe9592ce2169e3ad164ad8ab [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>
Ed Tanousabf2add2019-01-22 16:40:12 -080030#include <variant>
Ed Tanous1da66f72018-07-27 16:13:37 -070031
32namespace redfish
33{
34
Jason M. Bills424c4172019-03-21 13:50:33 -070035constexpr char const *CrashdumpObject = "com.intel.crashdump";
36constexpr char const *CrashdumpPath = "/com/intel/crashdump";
37constexpr char const *CrashdumpOnDemandPath = "/com/intel/crashdump/OnDemand";
38constexpr char const *CrashdumpInterface = "com.intel.crashdump";
39constexpr char const *CrashdumpOnDemandInterface =
40 "com.intel.crashdump.OnDemand";
41constexpr char const *CrashdumpRawPECIInterface =
42 "com.intel.crashdump.SendRawPeci";
Ed Tanous1da66f72018-07-27 16:13:37 -070043
Jason M. Bills4851d452019-03-28 11:27:48 -070044namespace message_registries
45{
46static const Message *getMessageFromRegistry(
47 const std::string &messageKey,
48 const boost::beast::span<const MessageEntry> registry)
49{
50 boost::beast::span<const MessageEntry>::const_iterator messageIt =
51 std::find_if(registry.cbegin(), registry.cend(),
52 [&messageKey](const MessageEntry &messageEntry) {
53 return !std::strcmp(messageEntry.first,
54 messageKey.c_str());
55 });
56 if (messageIt != registry.cend())
57 {
58 return &messageIt->second;
59 }
60
61 return nullptr;
62}
63
64static const Message *getMessage(const std::string_view &messageID)
65{
66 // Redfish MessageIds are in the form
67 // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find
68 // the right Message
69 std::vector<std::string> fields;
70 fields.reserve(4);
71 boost::split(fields, messageID, boost::is_any_of("."));
72 std::string &registryName = fields[0];
73 std::string &messageKey = fields[3];
74
75 // Find the right registry and check it for the MessageKey
76 if (std::string(base::header.registryPrefix) == registryName)
77 {
78 return getMessageFromRegistry(
79 messageKey, boost::beast::span<const MessageEntry>(base::registry));
80 }
81 if (std::string(openbmc::header.registryPrefix) == registryName)
82 {
83 return getMessageFromRegistry(
84 messageKey,
85 boost::beast::span<const MessageEntry>(openbmc::registry));
86 }
87 return nullptr;
88}
89} // namespace message_registries
90
James Feistf6150402019-01-08 10:36:20 -080091namespace fs = std::filesystem;
Ed Tanous1da66f72018-07-27 16:13:37 -070092
Andrew Geisslercb92c032018-08-17 07:56:14 -070093using GetManagedPropertyType = boost::container::flat_map<
94 std::string,
95 sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t,
96 int32_t, uint32_t, int64_t, uint64_t, double>>;
97
98using GetManagedObjectsType = boost::container::flat_map<
99 sdbusplus::message::object_path,
100 boost::container::flat_map<std::string, GetManagedPropertyType>>;
101
102inline std::string translateSeverityDbusToRedfish(const std::string &s)
103{
104 if (s == "xyz.openbmc_project.Logging.Entry.Level.Alert")
105 {
106 return "Critical";
107 }
108 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Critical")
109 {
110 return "Critical";
111 }
112 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Debug")
113 {
114 return "OK";
115 }
116 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency")
117 {
118 return "Critical";
119 }
120 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Error")
121 {
122 return "Critical";
123 }
124 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Informational")
125 {
126 return "OK";
127 }
128 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Notice")
129 {
130 return "OK";
131 }
132 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
133 {
134 return "Warning";
135 }
136 return "";
137}
138
Jason M. Bills16428a12018-11-02 12:42:29 -0700139static int getJournalMetadata(sd_journal *journal,
Ed Tanous39e77502019-03-04 17:35:53 -0800140 const std::string_view &field,
141 std::string_view &contents)
Jason M. Bills16428a12018-11-02 12:42:29 -0700142{
143 const char *data = nullptr;
144 size_t length = 0;
145 int ret = 0;
146 // Get the metadata from the requested field of the journal entry
Ed Tanousb01bf292019-03-25 19:25:26 +0000147 ret = sd_journal_get_data(journal, field.data(), (const void **)&data,
148 &length);
Jason M. Bills16428a12018-11-02 12:42:29 -0700149 if (ret < 0)
150 {
151 return ret;
152 }
Ed Tanous39e77502019-03-04 17:35:53 -0800153 contents = std::string_view(data, length);
Jason M. Bills16428a12018-11-02 12:42:29 -0700154 // Only use the content after the "=" character.
155 contents.remove_prefix(std::min(contents.find("=") + 1, contents.size()));
156 return ret;
157}
158
159static int getJournalMetadata(sd_journal *journal,
Ed Tanous39e77502019-03-04 17:35:53 -0800160 const std::string_view &field, const int &base,
Jason M. Bills16428a12018-11-02 12:42:29 -0700161 int &contents)
162{
163 int ret = 0;
Ed Tanous39e77502019-03-04 17:35:53 -0800164 std::string_view metadata;
Jason M. Bills16428a12018-11-02 12:42:29 -0700165 // Get the metadata from the requested field of the journal entry
166 ret = getJournalMetadata(journal, field, metadata);
167 if (ret < 0)
168 {
169 return ret;
170 }
Ed Tanousb01bf292019-03-25 19:25:26 +0000171 contents = strtol(metadata.data(), nullptr, base);
Jason M. Bills16428a12018-11-02 12:42:29 -0700172 return ret;
173}
174
175static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp)
176{
177 int ret = 0;
178 uint64_t timestamp = 0;
179 ret = sd_journal_get_realtime_usec(journal, &timestamp);
180 if (ret < 0)
181 {
182 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
183 << strerror(-ret);
184 return false;
185 }
186 time_t t =
187 static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s
188 struct tm *loctime = localtime(&t);
189 char entryTime[64] = {};
190 if (NULL != loctime)
191 {
192 strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime);
193 }
194 // Insert the ':' into the timezone
Ed Tanous39e77502019-03-04 17:35:53 -0800195 std::string_view t1(entryTime);
196 std::string_view t2(entryTime);
Jason M. Bills16428a12018-11-02 12:42:29 -0700197 if (t1.size() > 2 && t2.size() > 2)
198 {
199 t1.remove_suffix(2);
200 t2.remove_prefix(t2.size() - 2);
201 }
Ed Tanous39e77502019-03-04 17:35:53 -0800202 entryTimestamp = std::string(t1) + ":" + std::string(t2);
Jason M. Bills16428a12018-11-02 12:42:29 -0700203 return true;
204}
205
206static bool getSkipParam(crow::Response &res, const crow::Request &req,
207 long &skip)
208{
209 char *skipParam = req.urlParams.get("$skip");
210 if (skipParam != nullptr)
211 {
212 char *ptr = nullptr;
213 skip = std::strtol(skipParam, &ptr, 10);
214 if (*skipParam == '\0' || *ptr != '\0')
215 {
216
217 messages::queryParameterValueTypeError(res, std::string(skipParam),
218 "$skip");
219 return false;
220 }
221 if (skip < 0)
222 {
223
224 messages::queryParameterOutOfRange(res, std::to_string(skip),
225 "$skip", "greater than 0");
226 return false;
227 }
228 }
229 return true;
230}
231
232static constexpr const long maxEntriesPerPage = 1000;
233static bool getTopParam(crow::Response &res, const crow::Request &req,
234 long &top)
235{
236 char *topParam = req.urlParams.get("$top");
237 if (topParam != nullptr)
238 {
239 char *ptr = nullptr;
240 top = std::strtol(topParam, &ptr, 10);
241 if (*topParam == '\0' || *ptr != '\0')
242 {
243 messages::queryParameterValueTypeError(res, std::string(topParam),
244 "$top");
245 return false;
246 }
247 if (top < 1 || top > maxEntriesPerPage)
248 {
249
250 messages::queryParameterOutOfRange(
251 res, std::to_string(top), "$top",
252 "1-" + std::to_string(maxEntriesPerPage));
253 return false;
254 }
255 }
256 return true;
257}
258
259static bool getUniqueEntryID(sd_journal *journal, std::string &entryID)
260{
261 int ret = 0;
262 static uint64_t prevTs = 0;
263 static int index = 0;
264 // Get the entry timestamp
265 uint64_t curTs = 0;
266 ret = sd_journal_get_realtime_usec(journal, &curTs);
267 if (ret < 0)
268 {
269 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
270 << strerror(-ret);
271 return false;
272 }
273 // If the timestamp isn't unique, increment the index
274 if (curTs == prevTs)
275 {
276 index++;
277 }
278 else
279 {
280 // Otherwise, reset it
281 index = 0;
282 }
283 // Save the timestamp
284 prevTs = curTs;
285
286 entryID = std::to_string(curTs);
287 if (index > 0)
288 {
289 entryID += "_" + std::to_string(index);
290 }
291 return true;
292}
293
Jason M. Bills95820182019-04-22 16:25:34 -0700294static bool getUniqueEntryID(const std::string &logEntry, std::string &entryID)
295{
296 static uint64_t prevTs = 0;
297 static int index = 0;
298 // Get the entry timestamp
299 uint64_t curTs = 0;
300 std::tm timeStruct = {};
301 std::istringstream entryStream(logEntry);
302 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
303 {
304 curTs = std::mktime(&timeStruct);
305 }
306 // If the timestamp isn't unique, increment the index
307 if (curTs == prevTs)
308 {
309 index++;
310 }
311 else
312 {
313 // Otherwise, reset it
314 index = 0;
315 }
316 // Save the timestamp
317 prevTs = curTs;
318
319 entryID = std::to_string(curTs);
320 if (index > 0)
321 {
322 entryID += "_" + std::to_string(index);
323 }
324 return true;
325}
326
Jason M. Bills16428a12018-11-02 12:42:29 -0700327static bool getTimestampFromID(crow::Response &res, const std::string &entryID,
328 uint64_t &timestamp, uint16_t &index)
329{
330 if (entryID.empty())
331 {
332 return false;
333 }
334 // Convert the unique ID back to a timestamp to find the entry
Ed Tanous39e77502019-03-04 17:35:53 -0800335 std::string_view tsStr(entryID);
Jason M. Bills16428a12018-11-02 12:42:29 -0700336
337 auto underscorePos = tsStr.find("_");
338 if (underscorePos != tsStr.npos)
339 {
340 // Timestamp has an index
341 tsStr.remove_suffix(tsStr.size() - underscorePos);
Ed Tanous39e77502019-03-04 17:35:53 -0800342 std::string_view indexStr(entryID);
Jason M. Bills16428a12018-11-02 12:42:29 -0700343 indexStr.remove_prefix(underscorePos + 1);
344 std::size_t pos;
345 try
346 {
Ed Tanous39e77502019-03-04 17:35:53 -0800347 index = std::stoul(std::string(indexStr), &pos);
Jason M. Bills16428a12018-11-02 12:42:29 -0700348 }
Ed Tanousb01bf292019-03-25 19:25:26 +0000349 catch (std::invalid_argument)
Jason M. Bills16428a12018-11-02 12:42:29 -0700350 {
351 messages::resourceMissingAtURI(res, entryID);
352 return false;
353 }
Ed Tanousb01bf292019-03-25 19:25:26 +0000354 catch (std::out_of_range)
Jason M. Bills16428a12018-11-02 12:42:29 -0700355 {
356 messages::resourceMissingAtURI(res, entryID);
357 return false;
358 }
359 if (pos != indexStr.size())
360 {
361 messages::resourceMissingAtURI(res, entryID);
362 return false;
363 }
364 }
365 // Timestamp has no index
366 std::size_t pos;
367 try
368 {
Ed Tanous39e77502019-03-04 17:35:53 -0800369 timestamp = std::stoull(std::string(tsStr), &pos);
Jason M. Bills16428a12018-11-02 12:42:29 -0700370 }
Ed Tanousb01bf292019-03-25 19:25:26 +0000371 catch (std::invalid_argument)
Jason M. Bills16428a12018-11-02 12:42:29 -0700372 {
373 messages::resourceMissingAtURI(res, entryID);
374 return false;
375 }
Ed Tanousb01bf292019-03-25 19:25:26 +0000376 catch (std::out_of_range)
Jason M. Bills16428a12018-11-02 12:42:29 -0700377 {
378 messages::resourceMissingAtURI(res, entryID);
379 return false;
380 }
381 if (pos != tsStr.size())
382 {
383 messages::resourceMissingAtURI(res, entryID);
384 return false;
385 }
386 return true;
387}
388
Jason M. Bills95820182019-04-22 16:25:34 -0700389static bool
390 getRedfishLogFiles(std::vector<std::filesystem::path> &redfishLogFiles)
391{
392 static const std::filesystem::path redfishLogDir = "/var/log";
393 static const std::string redfishLogFilename = "redfish";
394
395 // Loop through the directory looking for redfish log files
396 for (const std::filesystem::directory_entry &dirEnt :
397 std::filesystem::directory_iterator(redfishLogDir))
398 {
399 // If we find a redfish log file, save the path
400 std::string filename = dirEnt.path().filename();
401 if (boost::starts_with(filename, redfishLogFilename))
402 {
403 redfishLogFiles.emplace_back(redfishLogDir / filename);
404 }
405 }
406 // As the log files rotate, they are appended with a ".#" that is higher for
407 // the older logs. Since we don't expect more than 10 log files, we
408 // can just sort the list to get them in order from newest to oldest
409 std::sort(redfishLogFiles.begin(), redfishLogFiles.end());
410
411 return !redfishLogFiles.empty();
412}
413
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800414class SystemLogServiceCollection : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700415{
416 public:
417 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800418 SystemLogServiceCollection(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800419 Node(app, "/redfish/v1/Systems/system/LogServices/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800420 {
421 entityPrivileges = {
422 {boost::beast::http::verb::get, {{"Login"}}},
423 {boost::beast::http::verb::head, {{"Login"}}},
424 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
425 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
426 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
427 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
428 }
429
430 private:
431 /**
432 * Functions triggers appropriate requests on DBus
433 */
434 void doGet(crow::Response &res, const crow::Request &req,
435 const std::vector<std::string> &params) override
436 {
437 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800438 // Collections don't include the static data added by SubRoute because
439 // it has a duplicate entry for members
440 asyncResp->res.jsonValue["@odata.type"] =
441 "#LogServiceCollection.LogServiceCollection";
442 asyncResp->res.jsonValue["@odata.context"] =
443 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
444 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800445 "/redfish/v1/Systems/system/LogServices";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800446 asyncResp->res.jsonValue["Name"] = "System Log Services Collection";
447 asyncResp->res.jsonValue["Description"] =
448 "Collection of LogServices for this Computer System";
449 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
450 logServiceArray = nlohmann::json::array();
Ed Tanous029573d2019-02-01 10:57:49 -0800451 logServiceArray.push_back(
452 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}});
Jason M. Billsd53dd412019-02-12 17:16:22 -0800453#ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
454 logServiceArray.push_back(
Andrew Geisslercb92c032018-08-17 07:56:14 -0700455 {{ "@odata.id",
Jason M. Bills424c4172019-03-21 13:50:33 -0700456 "/redfish/v1/Systems/system/LogServices/Crashdump" }});
Jason M. Billsd53dd412019-02-12 17:16:22 -0800457#endif
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800458 asyncResp->res.jsonValue["Members@odata.count"] =
459 logServiceArray.size();
460 }
461};
462
463class EventLogService : public Node
464{
465 public:
466 template <typename CrowApp>
467 EventLogService(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800468 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800469 {
470 entityPrivileges = {
471 {boost::beast::http::verb::get, {{"Login"}}},
472 {boost::beast::http::verb::head, {{"Login"}}},
473 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
474 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
475 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
476 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
477 }
478
479 private:
480 void doGet(crow::Response &res, const crow::Request &req,
481 const std::vector<std::string> &params) override
482 {
483 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
484
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800485 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800486 "/redfish/v1/Systems/system/LogServices/EventLog";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800487 asyncResp->res.jsonValue["@odata.type"] =
488 "#LogService.v1_1_0.LogService";
489 asyncResp->res.jsonValue["@odata.context"] =
490 "/redfish/v1/$metadata#LogService.LogService";
491 asyncResp->res.jsonValue["Name"] = "Event Log Service";
492 asyncResp->res.jsonValue["Description"] = "System Event Log Service";
493 asyncResp->res.jsonValue["Id"] = "Event Log";
494 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
495 asyncResp->res.jsonValue["Entries"] = {
496 {"@odata.id",
Ed Tanous029573d2019-02-01 10:57:49 -0800497 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}};
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800498 }
499};
500
Jason M. Bills95820182019-04-22 16:25:34 -0700501static int fillEventLogEntryJson(const std::string &logEntryID,
502 const std::string logEntry,
503 nlohmann::json &logEntryJson)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800504{
Jason M. Bills95820182019-04-22 16:25:34 -0700505 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
506 // Use split to separate the entry into its fields
507 std::vector<std::string> logEntryFields;
508 boost::split(logEntryFields, logEntry, boost::is_any_of(" ,"),
509 boost::token_compress_on);
510 // We need at least a MessageId to be valid
511 if (logEntryFields.size() < 2)
512 {
513 return 1;
514 }
515 std::string &timestamp = logEntryFields[0];
516 std::string &messageID = logEntryFields[1];
517 std::string &messageArgsStart = logEntryFields[2];
518 std::size_t messageArgsSize = logEntryFields.size() - 2;
519
Jason M. Bills4851d452019-03-28 11:27:48 -0700520 // Get the Message from the MessageRegistry
521 const message_registries::Message *message =
522 message_registries::getMessage(messageID);
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800523
Jason M. Bills4851d452019-03-28 11:27:48 -0700524 std::string msg;
525 std::string severity;
526 if (message != nullptr)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800527 {
Jason M. Bills4851d452019-03-28 11:27:48 -0700528 msg = message->message;
529 severity = message->severity;
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800530 }
531
Jason M. Bills95820182019-04-22 16:25:34 -0700532 // Get the MessageArgs from the log
533 boost::beast::span messageArgs(&messageArgsStart, messageArgsSize);
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800534
Jason M. Bills4851d452019-03-28 11:27:48 -0700535 // Fill the MessageArgs into the Message
Jason M. Bills95820182019-04-22 16:25:34 -0700536 int i = 0;
537 for (const std::string &messageArg : messageArgs)
Jason M. Bills4851d452019-03-28 11:27:48 -0700538 {
Jason M. Bills95820182019-04-22 16:25:34 -0700539 std::string argStr = "%" + std::to_string(++i);
Jason M. Bills4851d452019-03-28 11:27:48 -0700540 size_t argPos = msg.find(argStr);
541 if (argPos != std::string::npos)
542 {
Jason M. Bills95820182019-04-22 16:25:34 -0700543 msg.replace(argPos, argStr.length(), messageArg);
Jason M. Bills4851d452019-03-28 11:27:48 -0700544 }
545 }
546
Jason M. Bills95820182019-04-22 16:25:34 -0700547 // Get the Created time from the timestamp. The log timestamp is in RFC3339
548 // format which matches the Redfish format except for the fractional seconds
549 // between the '.' and the '+', so just remove them.
550 std::size_t dot = timestamp.find_first_of(".");
551 std::size_t plus = timestamp.find_first_of("+");
552 if (dot != std::string::npos && plus != std::string::npos)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800553 {
Jason M. Bills95820182019-04-22 16:25:34 -0700554 timestamp.erase(dot, plus - dot);
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800555 }
556
557 // Fill in the log entry with the gathered data
Jason M. Bills95820182019-04-22 16:25:34 -0700558 logEntryJson = {
Andrew Geisslercb92c032018-08-17 07:56:14 -0700559 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800560 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Ed Tanous029573d2019-02-01 10:57:49 -0800561 {"@odata.id",
Jason M. Bills95820182019-04-22 16:25:34 -0700562 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/#" +
563 logEntryID},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800564 {"Name", "System Event Log Entry"},
Jason M. Bills95820182019-04-22 16:25:34 -0700565 {"Id", logEntryID},
566 {"Message", std::move(msg)},
567 {"MessageId", std::move(messageID)},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800568 {"MessageArgs", std::move(messageArgs)},
569 {"EntryType", "Event"},
Jason M. Bills95820182019-04-22 16:25:34 -0700570 {"Severity", std::move(severity)},
571 {"Created", std::move(timestamp)}};
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800572 return 0;
573}
574
575class EventLogEntryCollection : public Node
576{
577 public:
578 template <typename CrowApp>
579 EventLogEntryCollection(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800580 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800581 {
582 entityPrivileges = {
583 {boost::beast::http::verb::get, {{"Login"}}},
584 {boost::beast::http::verb::head, {{"Login"}}},
585 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
586 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
587 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
588 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
589 }
590
591 private:
592 void doGet(crow::Response &res, const crow::Request &req,
593 const std::vector<std::string> &params) override
594 {
595 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
596 long skip = 0;
597 long top = maxEntriesPerPage; // Show max entries by default
598 if (!getSkipParam(asyncResp->res, req, skip))
599 {
600 return;
601 }
602 if (!getTopParam(asyncResp->res, req, top))
603 {
604 return;
605 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800606 // Collections don't include the static data added by SubRoute because
607 // it has a duplicate entry for members
608 asyncResp->res.jsonValue["@odata.type"] =
609 "#LogEntryCollection.LogEntryCollection";
610 asyncResp->res.jsonValue["@odata.context"] =
611 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
612 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800613 "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800614 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
615 asyncResp->res.jsonValue["Description"] =
616 "Collection of System Event Log Entries";
Andrew Geisslercb92c032018-08-17 07:56:14 -0700617
618#ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800619 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
620 logEntryArray = nlohmann::json::array();
Jason M. Bills95820182019-04-22 16:25:34 -0700621 // Go through the log files and create a unique ID for each entry
622 std::vector<std::filesystem::path> redfishLogFiles;
623 getRedfishLogFiles(redfishLogFiles);
Ed Tanousb01bf292019-03-25 19:25:26 +0000624 uint64_t entryCount = 0;
Jason M. Bills95820182019-04-22 16:25:34 -0700625
626 // Oldest logs are in the last file, so start there and loop backwards
627 for (size_t i = redfishLogFiles.size() - 1; i >= 0; i--)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800628 {
Jason M. Bills95820182019-04-22 16:25:34 -0700629 std::ifstream logStream(redfishLogFiles[i]);
630 if (!logStream.is_open())
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800631 {
632 continue;
633 }
634
Jason M. Bills95820182019-04-22 16:25:34 -0700635 std::string logEntry;
636 while (std::getline(logStream, logEntry))
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800637 {
Jason M. Bills95820182019-04-22 16:25:34 -0700638 entryCount++;
639 // Handle paging using skip (number of entries to skip from the
640 // start) and top (number of entries to display)
641 if (entryCount <= skip || entryCount > skip + top)
642 {
643 continue;
644 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800645
Jason M. Bills95820182019-04-22 16:25:34 -0700646 std::string idStr;
647 if (!getUniqueEntryID(logEntry, idStr))
648 {
649 continue;
650 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800651
Jason M. Bills95820182019-04-22 16:25:34 -0700652 logEntryArray.push_back({});
653 nlohmann::json &bmcLogEntry = logEntryArray.back();
654 if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0)
655 {
656 messages::internalError(asyncResp->res);
657 return;
658 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800659 }
660 }
661 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
662 if (skip + top < entryCount)
663 {
664 asyncResp->res.jsonValue["Members@odata.nextLink"] =
Jason M. Bills95820182019-04-22 16:25:34 -0700665 "/redfish/v1/Systems/system/LogServices/EventLog/"
666 "Entries?$skip=" +
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800667 std::to_string(skip + top);
668 }
Andrew Geisslercb92c032018-08-17 07:56:14 -0700669#else
670 // DBus implementation of EventLog/Entries
671 // Make call to Logging Service to find all log entry objects
672 crow::connections::systemBus->async_method_call(
673 [asyncResp](const boost::system::error_code ec,
674 GetManagedObjectsType &resp) {
675 if (ec)
676 {
677 // TODO Handle for specific error code
678 BMCWEB_LOG_ERROR
679 << "getLogEntriesIfaceData resp_handler got error "
680 << ec;
681 messages::internalError(asyncResp->res);
682 return;
683 }
684 nlohmann::json &entriesArray =
685 asyncResp->res.jsonValue["Members"];
686 entriesArray = nlohmann::json::array();
687 for (auto &objectPath : resp)
688 {
689 for (auto &interfaceMap : objectPath.second)
690 {
691 if (interfaceMap.first !=
692 "xyz.openbmc_project.Logging.Entry")
693 {
694 BMCWEB_LOG_DEBUG << "Bailing early on "
695 << interfaceMap.first;
696 continue;
697 }
698 entriesArray.push_back({});
699 nlohmann::json &thisEntry = entriesArray.back();
700 uint32_t *id;
701 std::time_t timestamp;
702 std::string *severity, *message;
703 bool *resolved;
704 for (auto &propertyMap : interfaceMap.second)
705 {
706 if (propertyMap.first == "Id")
707 {
708 id = sdbusplus::message::variant_ns::get_if<
709 uint32_t>(&propertyMap.second);
710 if (id == nullptr)
711 {
712 messages::propertyMissing(asyncResp->res,
713 "Id");
714 }
715 }
716 else if (propertyMap.first == "Timestamp")
717 {
718 const uint64_t *millisTimeStamp =
719 std::get_if<uint64_t>(&propertyMap.second);
720 if (millisTimeStamp == nullptr)
721 {
722 messages::propertyMissing(asyncResp->res,
723 "Timestamp");
724 }
725 // Retrieve Created property with format:
726 // yyyy-mm-ddThh:mm:ss
727 std::chrono::milliseconds chronoTimeStamp(
728 *millisTimeStamp);
729 timestamp =
730 std::chrono::duration_cast<
731 std::chrono::seconds>(chronoTimeStamp)
732 .count();
733 }
734 else if (propertyMap.first == "Severity")
735 {
736 severity = std::get_if<std::string>(
737 &propertyMap.second);
738 if (severity == nullptr)
739 {
740 messages::propertyMissing(asyncResp->res,
741 "Severity");
742 }
743 }
744 else if (propertyMap.first == "Message")
745 {
746 message = std::get_if<std::string>(
747 &propertyMap.second);
748 if (message == nullptr)
749 {
750 messages::propertyMissing(asyncResp->res,
751 "Message");
752 }
753 }
754 }
755 thisEntry = {
756 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
757 {"@odata.context", "/redfish/v1/"
758 "$metadata#LogEntry.LogEntry"},
759 {"@odata.id",
760 "/redfish/v1/Systems/system/LogServices/EventLog/"
761 "Entries/" +
762 std::to_string(*id)},
763 {"Name", "System DBus Event Log Entry"},
764 {"Id", std::to_string(*id)},
765 {"Message", *message},
766 {"EntryType", "Event"},
767 {"Severity",
768 translateSeverityDbusToRedfish(*severity)},
769 {"Created", crow::utility::getDateTime(timestamp)}};
770 }
771 }
772 std::sort(entriesArray.begin(), entriesArray.end(),
773 [](const nlohmann::json &left,
774 const nlohmann::json &right) {
775 return (left["Id"] <= right["Id"]);
776 });
777 asyncResp->res.jsonValue["Members@odata.count"] =
778 entriesArray.size();
779 },
780 "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging",
781 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
782#endif
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800783 }
784};
785
786class EventLogEntry : public Node
787{
788 public:
789 EventLogEntry(CrowApp &app) :
790 Node(app,
Ed Tanous029573d2019-02-01 10:57:49 -0800791 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/",
792 std::string())
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800793 {
794 entityPrivileges = {
795 {boost::beast::http::verb::get, {{"Login"}}},
796 {boost::beast::http::verb::head, {{"Login"}}},
797 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
798 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
799 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
800 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
801 }
802
803 private:
804 void doGet(crow::Response &res, const crow::Request &req,
805 const std::vector<std::string> &params) override
806 {
807 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous029573d2019-02-01 10:57:49 -0800808 if (params.size() != 1)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800809 {
810 messages::internalError(asyncResp->res);
811 return;
812 }
Ed Tanous029573d2019-02-01 10:57:49 -0800813 const std::string &entryID = params[0];
Andrew Geisslercb92c032018-08-17 07:56:14 -0700814
Jason M. Bills95820182019-04-22 16:25:34 -0700815#ifdef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
Andrew Geisslercb92c032018-08-17 07:56:14 -0700816 // DBus implementation of EventLog/Entries
817 // Make call to Logging Service to find all log entry objects
818 crow::connections::systemBus->async_method_call(
819 [asyncResp, entryID](const boost::system::error_code ec,
820 GetManagedPropertyType &resp) {
821 if (ec)
822 {
823 BMCWEB_LOG_ERROR
824 << "EventLogEntry (DBus) resp_handler got error " << ec;
825 messages::internalError(asyncResp->res);
826 return;
827 }
828 uint32_t *id;
829 std::time_t timestamp;
830 std::string *severity, *message;
831 bool *resolved;
832 for (auto &propertyMap : resp)
833 {
834 if (propertyMap.first == "Id")
835 {
836 id = std::get_if<uint32_t>(&propertyMap.second);
837 if (id == nullptr)
838 {
839 messages::propertyMissing(asyncResp->res, "Id");
840 }
841 }
842 else if (propertyMap.first == "Timestamp")
843 {
844 const uint64_t *millisTimeStamp =
845 std::get_if<uint64_t>(&propertyMap.second);
846 if (millisTimeStamp == nullptr)
847 {
848 messages::propertyMissing(asyncResp->res,
849 "Timestamp");
850 }
851 // Retrieve Created property with format:
852 // yyyy-mm-ddThh:mm:ss
853 std::chrono::milliseconds chronoTimeStamp(
854 *millisTimeStamp);
855 timestamp =
856 std::chrono::duration_cast<std::chrono::seconds>(
857 chronoTimeStamp)
858 .count();
859 }
860 else if (propertyMap.first == "Severity")
861 {
862 severity =
863 std::get_if<std::string>(&propertyMap.second);
864 if (severity == nullptr)
865 {
866 messages::propertyMissing(asyncResp->res,
867 "Severity");
868 }
869 }
870 else if (propertyMap.first == "Message")
871 {
872 message = std::get_if<std::string>(&propertyMap.second);
873 if (message == nullptr)
874 {
875 messages::propertyMissing(asyncResp->res,
876 "Message");
877 }
878 }
879 }
880 asyncResp->res.jsonValue = {
881 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
882 {"@odata.context", "/redfish/v1/"
883 "$metadata#LogEntry.LogEntry"},
884 {"@odata.id",
885 "/redfish/v1/Systems/system/LogServices/EventLog/"
886 "Entries/" +
887 std::to_string(*id)},
888 {"Name", "System DBus Event Log Entry"},
889 {"Id", std::to_string(*id)},
890 {"Message", *message},
891 {"EntryType", "Event"},
892 {"Severity", translateSeverityDbusToRedfish(*severity)},
Jason M. Bills95820182019-04-22 16:25:34 -0700893 { "Created",
894 crow::utility::getDateTime(timestamp) }};
Andrew Geisslercb92c032018-08-17 07:56:14 -0700895 },
896 "xyz.openbmc_project.Logging",
897 "/xyz/openbmc_project/logging/entry/" + entryID,
898 "org.freedesktop.DBus.Properties", "GetAll",
899 "xyz.openbmc_project.Logging.Entry");
900#endif
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800901 }
902};
903
904class BMCLogServiceCollection : public Node
905{
906 public:
907 template <typename CrowApp>
908 BMCLogServiceCollection(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700909 Node(app, "/redfish/v1/Managers/bmc/LogServices/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700910 {
Ed Tanous1da66f72018-07-27 16:13:37 -0700911 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700912 {boost::beast::http::verb::get, {{"Login"}}},
913 {boost::beast::http::verb::head, {{"Login"}}},
914 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
915 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
916 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
917 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700918 }
919
920 private:
921 /**
922 * Functions triggers appropriate requests on DBus
923 */
924 void doGet(crow::Response &res, const crow::Request &req,
925 const std::vector<std::string> &params) override
926 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700927 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700928 // Collections don't include the static data added by SubRoute because
929 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -0700930 asyncResp->res.jsonValue["@odata.type"] =
Ed Tanous1da66f72018-07-27 16:13:37 -0700931 "#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700932 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800933 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700934 asyncResp->res.jsonValue["@odata.id"] =
935 "/redfish/v1/Managers/bmc/LogServices";
936 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
937 asyncResp->res.jsonValue["Description"] =
Ed Tanous1da66f72018-07-27 16:13:37 -0700938 "Collection of LogServices for this Manager";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800939 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
940 logServiceArray = nlohmann::json::array();
941#ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
942 logServiceArray.push_back(
Andrew Geisslercb92c032018-08-17 07:56:14 -0700943 {{ "@odata.id",
944 "/redfish/v1/Managers/bmc/LogServices/Journal" }});
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800945#endif
Jason M. Billse1f26342018-07-18 12:12:00 -0700946 asyncResp->res.jsonValue["Members@odata.count"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800947 logServiceArray.size();
Ed Tanous1da66f72018-07-27 16:13:37 -0700948 }
949};
950
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800951class BMCJournalLogService : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700952{
953 public:
954 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800955 BMCJournalLogService(CrowApp &app) :
956 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
Jason M. Billse1f26342018-07-18 12:12:00 -0700957 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700958 entityPrivileges = {
959 {boost::beast::http::verb::get, {{"Login"}}},
960 {boost::beast::http::verb::head, {{"Login"}}},
961 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
962 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
963 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
964 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
965 }
966
967 private:
968 void doGet(crow::Response &res, const crow::Request &req,
969 const std::vector<std::string> &params) override
970 {
971 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700972 asyncResp->res.jsonValue["@odata.type"] =
973 "#LogService.v1_1_0.LogService";
Ed Tanous0f74e642018-11-12 15:17:05 -0800974 asyncResp->res.jsonValue["@odata.id"] =
975 "/redfish/v1/Managers/bmc/LogServices/Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -0700976 asyncResp->res.jsonValue["@odata.context"] =
977 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800978 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
979 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
980 asyncResp->res.jsonValue["Id"] = "BMC Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -0700981 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
Jason M. Billscd50aa42019-02-12 17:09:02 -0800982 asyncResp->res.jsonValue["Entries"] = {
983 {"@odata.id",
984 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/"}};
Jason M. Billse1f26342018-07-18 12:12:00 -0700985 }
986};
987
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800988static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID,
989 sd_journal *journal,
990 nlohmann::json &bmcJournalLogEntryJson)
Jason M. Billse1f26342018-07-18 12:12:00 -0700991{
992 // Get the Log Entry contents
993 int ret = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -0700994
Ed Tanous39e77502019-03-04 17:35:53 -0800995 std::string_view msg;
Jason M. Bills16428a12018-11-02 12:42:29 -0700996 ret = getJournalMetadata(journal, "MESSAGE", msg);
Jason M. Billse1f26342018-07-18 12:12:00 -0700997 if (ret < 0)
998 {
999 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
1000 return 1;
1001 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001002
1003 // Get the severity from the PRIORITY field
Jason M. Billse1f26342018-07-18 12:12:00 -07001004 int severity = 8; // Default to an invalid priority
Jason M. Bills16428a12018-11-02 12:42:29 -07001005 ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
Jason M. Billse1f26342018-07-18 12:12:00 -07001006 if (ret < 0)
1007 {
1008 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
1009 return 1;
1010 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001011
1012 // Get the Created time from the timestamp
Jason M. Bills16428a12018-11-02 12:42:29 -07001013 std::string entryTimeStr;
1014 if (!getEntryTimestamp(journal, entryTimeStr))
Jason M. Billse1f26342018-07-18 12:12:00 -07001015 {
Jason M. Bills16428a12018-11-02 12:42:29 -07001016 return 1;
Jason M. Billse1f26342018-07-18 12:12:00 -07001017 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001018
1019 // Fill in the log entry with the gathered data
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001020 bmcJournalLogEntryJson = {
Andrew Geisslercb92c032018-08-17 07:56:14 -07001021 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
Jason M. Billse1f26342018-07-18 12:12:00 -07001022 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001023 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
1024 bmcJournalLogEntryID},
Jason M. Billse1f26342018-07-18 12:12:00 -07001025 {"Name", "BMC Journal Entry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001026 {"Id", bmcJournalLogEntryID},
Jason M. Bills16428a12018-11-02 12:42:29 -07001027 {"Message", msg},
Jason M. Billse1f26342018-07-18 12:12:00 -07001028 {"EntryType", "Oem"},
1029 {"Severity",
1030 severity <= 2 ? "Critical"
1031 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
1032 {"OemRecordFormat", "Intel BMC Journal Entry"},
1033 {"Created", std::move(entryTimeStr)}};
1034 return 0;
1035}
1036
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001037class BMCJournalLogEntryCollection : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -07001038{
1039 public:
1040 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001041 BMCJournalLogEntryCollection(CrowApp &app) :
1042 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
Jason M. Billse1f26342018-07-18 12:12:00 -07001043 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001044 entityPrivileges = {
1045 {boost::beast::http::verb::get, {{"Login"}}},
1046 {boost::beast::http::verb::head, {{"Login"}}},
1047 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1048 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1049 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1050 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1051 }
1052
1053 private:
1054 void doGet(crow::Response &res, const crow::Request &req,
1055 const std::vector<std::string> &params) override
1056 {
1057 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001058 static constexpr const long maxEntriesPerPage = 1000;
1059 long skip = 0;
1060 long top = maxEntriesPerPage; // Show max entries by default
Jason M. Bills16428a12018-11-02 12:42:29 -07001061 if (!getSkipParam(asyncResp->res, req, skip))
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001062 {
Jason M. Bills16428a12018-11-02 12:42:29 -07001063 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001064 }
Jason M. Bills16428a12018-11-02 12:42:29 -07001065 if (!getTopParam(asyncResp->res, req, top))
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001066 {
Jason M. Bills16428a12018-11-02 12:42:29 -07001067 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001068 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001069 // Collections don't include the static data added by SubRoute because
1070 // it has a duplicate entry for members
1071 asyncResp->res.jsonValue["@odata.type"] =
1072 "#LogEntryCollection.LogEntryCollection";
Ed Tanous0f74e642018-11-12 15:17:05 -08001073 asyncResp->res.jsonValue["@odata.id"] =
1074 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001075 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001076 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -07001077 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001078 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001079 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
1080 asyncResp->res.jsonValue["Description"] =
1081 "Collection of BMC Journal Entries";
Ed Tanous0f74e642018-11-12 15:17:05 -08001082 asyncResp->res.jsonValue["@odata.id"] =
1083 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001084 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1085 logEntryArray = nlohmann::json::array();
1086
1087 // Go through the journal and use the timestamp to create a unique ID
1088 // for each entry
1089 sd_journal *journalTmp = nullptr;
1090 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1091 if (ret < 0)
1092 {
1093 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -07001094 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001095 return;
1096 }
1097 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1098 journalTmp, sd_journal_close);
1099 journalTmp = nullptr;
Ed Tanousb01bf292019-03-25 19:25:26 +00001100 uint64_t entryCount = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -07001101 SD_JOURNAL_FOREACH(journal.get())
1102 {
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001103 entryCount++;
1104 // Handle paging using skip (number of entries to skip from the
1105 // start) and top (number of entries to display)
1106 if (entryCount <= skip || entryCount > skip + top)
1107 {
1108 continue;
1109 }
1110
Jason M. Bills16428a12018-11-02 12:42:29 -07001111 std::string idStr;
1112 if (!getUniqueEntryID(journal.get(), idStr))
Jason M. Billse1f26342018-07-18 12:12:00 -07001113 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001114 continue;
1115 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001116
Jason M. Billse1f26342018-07-18 12:12:00 -07001117 logEntryArray.push_back({});
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001118 nlohmann::json &bmcJournalLogEntry = logEntryArray.back();
1119 if (fillBMCJournalLogEntryJson(idStr, journal.get(),
1120 bmcJournalLogEntry) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -07001121 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001122 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001123 return;
1124 }
1125 }
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001126 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
1127 if (skip + top < entryCount)
1128 {
1129 asyncResp->res.jsonValue["Members@odata.nextLink"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001130 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001131 std::to_string(skip + top);
1132 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001133 }
1134};
1135
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001136class BMCJournalLogEntry : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -07001137{
1138 public:
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001139 BMCJournalLogEntry(CrowApp &app) :
1140 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/",
Jason M. Billse1f26342018-07-18 12:12:00 -07001141 std::string())
1142 {
1143 entityPrivileges = {
1144 {boost::beast::http::verb::get, {{"Login"}}},
1145 {boost::beast::http::verb::head, {{"Login"}}},
1146 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1147 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1148 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1149 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1150 }
1151
1152 private:
1153 void doGet(crow::Response &res, const crow::Request &req,
1154 const std::vector<std::string> &params) override
1155 {
1156 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1157 if (params.size() != 1)
1158 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001159 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001160 return;
1161 }
Jason M. Bills16428a12018-11-02 12:42:29 -07001162 const std::string &entryID = params[0];
Jason M. Billse1f26342018-07-18 12:12:00 -07001163 // Convert the unique ID back to a timestamp to find the entry
Jason M. Billse1f26342018-07-18 12:12:00 -07001164 uint64_t ts = 0;
1165 uint16_t index = 0;
Jason M. Bills16428a12018-11-02 12:42:29 -07001166 if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
Jason M. Billse1f26342018-07-18 12:12:00 -07001167 {
Jason M. Bills16428a12018-11-02 12:42:29 -07001168 return;
Jason M. Billse1f26342018-07-18 12:12:00 -07001169 }
1170
1171 sd_journal *journalTmp = nullptr;
1172 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1173 if (ret < 0)
1174 {
1175 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -07001176 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001177 return;
1178 }
1179 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1180 journalTmp, sd_journal_close);
1181 journalTmp = nullptr;
1182 // Go to the timestamp in the log and move to the entry at the index
1183 ret = sd_journal_seek_realtime_usec(journal.get(), ts);
1184 for (int i = 0; i <= index; i++)
1185 {
1186 sd_journal_next(journal.get());
1187 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001188 // Confirm that the entry ID matches what was requested
1189 std::string idStr;
1190 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
1191 {
1192 messages::resourceMissingAtURI(asyncResp->res, entryID);
1193 return;
1194 }
1195
1196 if (fillBMCJournalLogEntryJson(entryID, journal.get(),
1197 asyncResp->res.jsonValue) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -07001198 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001199 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001200 return;
1201 }
1202 }
1203};
1204
Jason M. Bills424c4172019-03-21 13:50:33 -07001205class CrashdumpService : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -07001206{
1207 public:
1208 template <typename CrowApp>
Jason M. Bills424c4172019-03-21 13:50:33 -07001209 CrashdumpService(CrowApp &app) :
1210 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001211 {
Ed Tanous1da66f72018-07-27 16:13:37 -07001212 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001213 {boost::beast::http::verb::get, {{"Login"}}},
1214 {boost::beast::http::verb::head, {{"Login"}}},
1215 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1216 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1217 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1218 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001219 }
1220
1221 private:
1222 /**
1223 * Functions triggers appropriate requests on DBus
1224 */
1225 void doGet(crow::Response &res, const crow::Request &req,
1226 const std::vector<std::string> &params) override
1227 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001228 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001229 // Copy over the static data to include the entries added by SubRoute
Ed Tanous0f74e642018-11-12 15:17:05 -08001230 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Bills424c4172019-03-21 13:50:33 -07001231 "/redfish/v1/Systems/system/LogServices/Crashdump";
Jason M. Billse1f26342018-07-18 12:12:00 -07001232 asyncResp->res.jsonValue["@odata.type"] =
1233 "#LogService.v1_1_0.LogService";
1234 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001235 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Bills424c4172019-03-21 13:50:33 -07001236 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Service";
1237 asyncResp->res.jsonValue["Description"] = "Crashdump Service";
1238 asyncResp->res.jsonValue["Id"] = "Crashdump";
Jason M. Billse1f26342018-07-18 12:12:00 -07001239 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
1240 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
Jason M. Billscd50aa42019-02-12 17:09:02 -08001241 asyncResp->res.jsonValue["Entries"] = {
1242 {"@odata.id",
Jason M. Bills424c4172019-03-21 13:50:33 -07001243 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}};
Jason M. Billse1f26342018-07-18 12:12:00 -07001244 asyncResp->res.jsonValue["Actions"] = {
Ed Tanous1da66f72018-07-27 16:13:37 -07001245 {"Oem",
Jason M. Bills424c4172019-03-21 13:50:33 -07001246 {{"#Crashdump.OnDemand",
1247 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1248 "Actions/Oem/Crashdump.OnDemand"}}}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001249
1250#ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
Jason M. Billse1f26342018-07-18 12:12:00 -07001251 asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
Jason M. Bills424c4172019-03-21 13:50:33 -07001252 {"#Crashdump.SendRawPeci",
Andrew Geisslercb92c032018-08-17 07:56:14 -07001253 { { "target",
Jason M. Bills424c4172019-03-21 13:50:33 -07001254 "/redfish/v1/Systems/system/LogServices/Crashdump/"
1255 "Actions/Oem/Crashdump.SendRawPeci" } }});
Ed Tanous1da66f72018-07-27 16:13:37 -07001256#endif
Ed Tanous1da66f72018-07-27 16:13:37 -07001257 }
1258};
1259
Jason M. Bills424c4172019-03-21 13:50:33 -07001260class CrashdumpEntryCollection : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001261{
1262 public:
1263 template <typename CrowApp>
Jason M. Bills424c4172019-03-21 13:50:33 -07001264 CrashdumpEntryCollection(CrowApp &app) :
1265 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001266 {
Ed Tanous1da66f72018-07-27 16:13:37 -07001267 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001268 {boost::beast::http::verb::get, {{"Login"}}},
1269 {boost::beast::http::verb::head, {{"Login"}}},
1270 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1271 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1272 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1273 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001274 }
1275
1276 private:
1277 /**
1278 * Functions triggers appropriate requests on DBus
1279 */
1280 void doGet(crow::Response &res, const crow::Request &req,
1281 const std::vector<std::string> &params) override
1282 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001283 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001284 // Collections don't include the static data added by SubRoute because
1285 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -07001286 auto getLogEntriesCallback = [asyncResp](
1287 const boost::system::error_code ec,
1288 const std::vector<std::string> &resp) {
1289 if (ec)
1290 {
1291 if (ec.value() !=
1292 boost::system::errc::no_such_file_or_directory)
Ed Tanous1da66f72018-07-27 16:13:37 -07001293 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001294 BMCWEB_LOG_DEBUG << "failed to get entries ec: "
1295 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001296 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001297 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07001298 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001299 }
1300 asyncResp->res.jsonValue["@odata.type"] =
1301 "#LogEntryCollection.LogEntryCollection";
Ed Tanous0f74e642018-11-12 15:17:05 -08001302 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Bills424c4172019-03-21 13:50:33 -07001303 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001304 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsd53dd412019-02-12 17:16:22 -08001305 "/redfish/v1/"
1306 "$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Bills424c4172019-03-21 13:50:33 -07001307 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001308 asyncResp->res.jsonValue["Description"] =
Jason M. Bills424c4172019-03-21 13:50:33 -07001309 "Collection of Crashdump Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001310 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1311 logEntryArray = nlohmann::json::array();
1312 for (const std::string &objpath : resp)
1313 {
Jason M. Bills48e46392019-02-13 12:58:37 -08001314 // Don't list the on-demand log
Jason M. Bills424c4172019-03-21 13:50:33 -07001315 if (objpath.compare(CrashdumpOnDemandPath) == 0)
Ed Tanous1da66f72018-07-27 16:13:37 -07001316 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001317 continue;
Ed Tanous1da66f72018-07-27 16:13:37 -07001318 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001319 std::size_t lastPos = objpath.rfind("/");
1320 if (lastPos != std::string::npos)
1321 {
1322 logEntryArray.push_back(
Jason M. Billsd53dd412019-02-12 17:16:22 -08001323 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/"
Jason M. Bills424c4172019-03-21 13:50:33 -07001324 "Crashdump/Entries/" +
Jason M. Billse1f26342018-07-18 12:12:00 -07001325 objpath.substr(lastPos + 1)}});
1326 }
1327 }
1328 asyncResp->res.jsonValue["Members@odata.count"] =
1329 logEntryArray.size();
1330 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001331 crow::connections::systemBus->async_method_call(
1332 std::move(getLogEntriesCallback),
1333 "xyz.openbmc_project.ObjectMapper",
1334 "/xyz/openbmc_project/object_mapper",
1335 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
Jason M. Bills424c4172019-03-21 13:50:33 -07001336 std::array<const char *, 1>{CrashdumpInterface});
Ed Tanous1da66f72018-07-27 16:13:37 -07001337 }
1338};
1339
Jason M. Bills424c4172019-03-21 13:50:33 -07001340std::string getLogCreatedTime(const nlohmann::json &Crashdump)
Ed Tanous1da66f72018-07-27 16:13:37 -07001341{
Jason M. Bills424c4172019-03-21 13:50:33 -07001342 nlohmann::json::const_iterator cdIt = Crashdump.find("crashlog_data");
1343 if (cdIt != Crashdump.end())
Ed Tanous1da66f72018-07-27 16:13:37 -07001344 {
Jason M. Billsc4d00432019-02-12 17:17:48 -08001345 nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO");
1346 if (siIt != cdIt->end())
Ed Tanous1da66f72018-07-27 16:13:37 -07001347 {
Jason M. Billsc4d00432019-02-12 17:17:48 -08001348 nlohmann::json::const_iterator tsIt = siIt->find("timestamp");
1349 if (tsIt != siIt->end())
Ed Tanous1da66f72018-07-27 16:13:37 -07001350 {
Jason M. Billsc4d00432019-02-12 17:17:48 -08001351 const std::string *logTime =
1352 tsIt->get_ptr<const std::string *>();
1353 if (logTime != nullptr)
1354 {
1355 return *logTime;
1356 }
Ed Tanous1da66f72018-07-27 16:13:37 -07001357 }
1358 }
1359 }
1360 BMCWEB_LOG_DEBUG << "failed to find log timestamp";
1361
1362 return std::string();
1363}
1364
Jason M. Bills424c4172019-03-21 13:50:33 -07001365class CrashdumpEntry : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001366{
1367 public:
Jason M. Bills424c4172019-03-21 13:50:33 -07001368 CrashdumpEntry(CrowApp &app) :
Jason M. Billsd53dd412019-02-12 17:16:22 -08001369 Node(app,
Jason M. Bills424c4172019-03-21 13:50:33 -07001370 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/",
Ed Tanous1da66f72018-07-27 16:13:37 -07001371 std::string())
1372 {
1373 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001374 {boost::beast::http::verb::get, {{"Login"}}},
1375 {boost::beast::http::verb::head, {{"Login"}}},
1376 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1377 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1378 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1379 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001380 }
1381
1382 private:
1383 void doGet(crow::Response &res, const crow::Request &req,
1384 const std::vector<std::string> &params) override
1385 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001386 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001387 if (params.size() != 1)
1388 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001389 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001390 return;
1391 }
Ed Tanousb01bf292019-03-25 19:25:26 +00001392 const uint8_t logId = std::atoi(params[0].c_str());
Ed Tanousabf2add2019-01-22 16:40:12 -08001393 auto getStoredLogCallback = [asyncResp, logId](
1394 const boost::system::error_code ec,
1395 const std::variant<std::string> &resp) {
1396 if (ec)
1397 {
1398 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
1399 messages::internalError(asyncResp->res);
1400 return;
1401 }
1402 const std::string *log = std::get_if<std::string>(&resp);
1403 if (log == nullptr)
1404 {
1405 messages::internalError(asyncResp->res);
1406 return;
1407 }
1408 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1409 if (j.is_discarded())
1410 {
1411 messages::internalError(asyncResp->res);
1412 return;
1413 }
1414 std::string t = getLogCreatedTime(j);
1415 asyncResp->res.jsonValue = {
Andrew Geisslercb92c032018-08-17 07:56:14 -07001416 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
Ed Tanousabf2add2019-01-22 16:40:12 -08001417 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1418 {"@odata.id",
Jason M. Bills424c4172019-03-21 13:50:33 -07001419 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
Ed Tanousabf2add2019-01-22 16:40:12 -08001420 std::to_string(logId)},
Jason M. Bills424c4172019-03-21 13:50:33 -07001421 {"Name", "CPU Crashdump"},
Ed Tanousabf2add2019-01-22 16:40:12 -08001422 {"Id", logId},
1423 {"EntryType", "Oem"},
Jason M. Bills424c4172019-03-21 13:50:33 -07001424 {"OemRecordFormat", "Intel Crashdump"},
Ed Tanousabf2add2019-01-22 16:40:12 -08001425 {"Oem", {{"Intel", std::move(j)}}},
1426 {"Created", std::move(t)}};
1427 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001428 crow::connections::systemBus->async_method_call(
Jason M. Bills424c4172019-03-21 13:50:33 -07001429 std::move(getStoredLogCallback), CrashdumpObject,
1430 CrashdumpPath + std::string("/") + std::to_string(logId),
1431 "org.freedesktop.DBus.Properties", "Get", CrashdumpInterface,
1432 "Log");
Ed Tanous1da66f72018-07-27 16:13:37 -07001433 }
1434};
1435
Jason M. Bills424c4172019-03-21 13:50:33 -07001436class OnDemandCrashdump : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001437{
1438 public:
Jason M. Bills424c4172019-03-21 13:50:33 -07001439 OnDemandCrashdump(CrowApp &app) :
1440 Node(app,
1441 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
1442 "Crashdump.OnDemand/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001443 {
1444 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001445 {boost::beast::http::verb::get, {{"Login"}}},
1446 {boost::beast::http::verb::head, {{"Login"}}},
1447 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1448 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1449 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1450 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001451 }
1452
1453 private:
1454 void doPost(crow::Response &res, const crow::Request &req,
1455 const std::vector<std::string> &params) override
1456 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001457 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Bills48e46392019-02-13 12:58:37 -08001458 static std::unique_ptr<sdbusplus::bus::match::match> onDemandLogMatcher;
Ed Tanous1da66f72018-07-27 16:13:37 -07001459
Jason M. Bills48e46392019-02-13 12:58:37 -08001460 // Only allow one OnDemand Log request at a time
1461 if (onDemandLogMatcher != nullptr)
Ed Tanous1da66f72018-07-27 16:13:37 -07001462 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001463 asyncResp->res.addHeader("Retry-After", "30");
Jason M. Billsf12894f2018-10-09 12:45:45 -07001464 messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
Ed Tanous1da66f72018-07-27 16:13:37 -07001465 return;
1466 }
1467 // Make this static so it survives outside this method
1468 static boost::asio::deadline_timer timeout(*req.ioService);
1469
1470 timeout.expires_from_now(boost::posix_time::seconds(30));
Jason M. Billse1f26342018-07-18 12:12:00 -07001471 timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
Jason M. Bills48e46392019-02-13 12:58:37 -08001472 onDemandLogMatcher = nullptr;
Ed Tanous1da66f72018-07-27 16:13:37 -07001473 if (ec)
1474 {
1475 // operation_aborted is expected if timer is canceled before
1476 // completion.
1477 if (ec != boost::asio::error::operation_aborted)
1478 {
1479 BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
1480 }
1481 return;
1482 }
Jason M. Bills48e46392019-02-13 12:58:37 -08001483 BMCWEB_LOG_ERROR << "Timed out waiting for on-demand log";
Ed Tanous1da66f72018-07-27 16:13:37 -07001484
Jason M. Billsf12894f2018-10-09 12:45:45 -07001485 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001486 });
1487
Jason M. Bills48e46392019-02-13 12:58:37 -08001488 auto onDemandLogMatcherCallback = [asyncResp](
1489 sdbusplus::message::message &m) {
1490 BMCWEB_LOG_DEBUG << "OnDemand log available match fired";
Ed Tanous1da66f72018-07-27 16:13:37 -07001491 boost::system::error_code ec;
1492 timeout.cancel(ec);
1493 if (ec)
1494 {
1495 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
1496 }
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001497 sdbusplus::message::object_path objPath;
Ed Tanous1da66f72018-07-27 16:13:37 -07001498 boost::container::flat_map<
Ed Tanousabf2add2019-01-22 16:40:12 -08001499 std::string, boost::container::flat_map<
1500 std::string, std::variant<std::string>>>
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001501 interfacesAdded;
1502 m.read(objPath, interfacesAdded);
Ed Tanousabf2add2019-01-22 16:40:12 -08001503 const std::string *log = std::get_if<std::string>(
Jason M. Bills424c4172019-03-21 13:50:33 -07001504 &interfacesAdded[CrashdumpInterface]["Log"]);
Ed Tanous1da66f72018-07-27 16:13:37 -07001505 if (log == nullptr)
1506 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001507 messages::internalError(asyncResp->res);
Jason M. Bills48e46392019-02-13 12:58:37 -08001508 // Careful with onDemandLogMatcher. It is a unique_ptr to the
Ed Tanous1da66f72018-07-27 16:13:37 -07001509 // match object inside which this lambda is executing. Once it
1510 // is set to nullptr, the match object will be destroyed and the
1511 // lambda will lose its context, including res, so it needs to
1512 // be the last thing done.
Jason M. Bills48e46392019-02-13 12:58:37 -08001513 onDemandLogMatcher = nullptr;
Ed Tanous1da66f72018-07-27 16:13:37 -07001514 return;
1515 }
1516 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1517 if (j.is_discarded())
1518 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001519 messages::internalError(asyncResp->res);
Jason M. Bills48e46392019-02-13 12:58:37 -08001520 // Careful with onDemandLogMatcher. It is a unique_ptr to the
Ed Tanous1da66f72018-07-27 16:13:37 -07001521 // match object inside which this lambda is executing. Once it
1522 // is set to nullptr, the match object will be destroyed and the
1523 // lambda will lose its context, including res, so it needs to
1524 // be the last thing done.
Jason M. Bills48e46392019-02-13 12:58:37 -08001525 onDemandLogMatcher = nullptr;
Ed Tanous1da66f72018-07-27 16:13:37 -07001526 return;
1527 }
1528 std::string t = getLogCreatedTime(j);
Jason M. Billse1f26342018-07-18 12:12:00 -07001529 asyncResp->res.jsonValue = {
Andrew Geisslercb92c032018-08-17 07:56:14 -07001530 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
Ed Tanous1da66f72018-07-27 16:13:37 -07001531 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Jason M. Bills424c4172019-03-21 13:50:33 -07001532 {"Name", "CPU Crashdump"},
Ed Tanous1da66f72018-07-27 16:13:37 -07001533 {"EntryType", "Oem"},
Jason M. Bills424c4172019-03-21 13:50:33 -07001534 {"OemRecordFormat", "Intel Crashdump"},
Ed Tanous1da66f72018-07-27 16:13:37 -07001535 {"Oem", {{"Intel", std::move(j)}}},
1536 {"Created", std::move(t)}};
Jason M. Bills48e46392019-02-13 12:58:37 -08001537 // Careful with onDemandLogMatcher. It is a unique_ptr to the
Ed Tanous1da66f72018-07-27 16:13:37 -07001538 // match object inside which this lambda is executing. Once it is
1539 // set to nullptr, the match object will be destroyed and the lambda
1540 // will lose its context, including res, so it needs to be the last
1541 // thing done.
Jason M. Bills48e46392019-02-13 12:58:37 -08001542 onDemandLogMatcher = nullptr;
Ed Tanous1da66f72018-07-27 16:13:37 -07001543 };
Jason M. Bills48e46392019-02-13 12:58:37 -08001544 onDemandLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
Ed Tanous1da66f72018-07-27 16:13:37 -07001545 *crow::connections::systemBus,
1546 sdbusplus::bus::match::rules::interfacesAdded() +
Jason M. Bills424c4172019-03-21 13:50:33 -07001547 sdbusplus::bus::match::rules::argNpath(0,
1548 CrashdumpOnDemandPath),
Jason M. Bills48e46392019-02-13 12:58:37 -08001549 std::move(onDemandLogMatcherCallback));
Ed Tanous1da66f72018-07-27 16:13:37 -07001550
Jason M. Bills48e46392019-02-13 12:58:37 -08001551 auto generateonDemandLogCallback =
Jason M. Billse1f26342018-07-18 12:12:00 -07001552 [asyncResp](const boost::system::error_code ec,
1553 const std::string &resp) {
Ed Tanous1da66f72018-07-27 16:13:37 -07001554 if (ec)
1555 {
1556 if (ec.value() ==
1557 boost::system::errc::operation_not_supported)
1558 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001559 messages::resourceInStandby(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001560 }
1561 else
1562 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001563 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001564 }
Ed Tanous1da66f72018-07-27 16:13:37 -07001565 boost::system::error_code timeoutec;
1566 timeout.cancel(timeoutec);
1567 if (timeoutec)
1568 {
1569 BMCWEB_LOG_ERROR << "error canceling timer "
1570 << timeoutec;
1571 }
Jason M. Bills48e46392019-02-13 12:58:37 -08001572 onDemandLogMatcher = nullptr;
Ed Tanous1da66f72018-07-27 16:13:37 -07001573 return;
1574 }
1575 };
1576 crow::connections::systemBus->async_method_call(
Jason M. Bills424c4172019-03-21 13:50:33 -07001577 std::move(generateonDemandLogCallback), CrashdumpObject,
1578 CrashdumpPath, CrashdumpOnDemandInterface, "GenerateOnDemandLog");
Ed Tanous1da66f72018-07-27 16:13:37 -07001579 }
1580};
1581
Jason M. Billse1f26342018-07-18 12:12:00 -07001582class SendRawPECI : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001583{
1584 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001585 SendRawPECI(CrowApp &app) :
Jason M. Bills424c4172019-03-21 13:50:33 -07001586 Node(app,
1587 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
1588 "Crashdump.SendRawPeci/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001589 {
1590 entityPrivileges = {
1591 {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
1592 {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
1593 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1594 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1595 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1596 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1597 }
1598
1599 private:
1600 void doPost(crow::Response &res, const crow::Request &req,
1601 const std::vector<std::string> &params) override
1602 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001603 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanousb1556422018-10-16 14:09:17 -07001604 uint8_t clientAddress = 0;
1605 uint8_t readLength = 0;
Ed Tanous1da66f72018-07-27 16:13:37 -07001606 std::vector<uint8_t> peciCommand;
Ed Tanousb1556422018-10-16 14:09:17 -07001607 if (!json_util::readJson(req, res, "ClientAddress", clientAddress,
1608 "ReadLength", readLength, "PECICommand",
1609 peciCommand))
Ed Tanous1da66f72018-07-27 16:13:37 -07001610 {
Ed Tanousb1556422018-10-16 14:09:17 -07001611 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07001612 }
Ed Tanousb1556422018-10-16 14:09:17 -07001613
Ed Tanous1da66f72018-07-27 16:13:37 -07001614 // Callback to return the Raw PECI response
Jason M. Billse1f26342018-07-18 12:12:00 -07001615 auto sendRawPECICallback =
1616 [asyncResp](const boost::system::error_code ec,
1617 const std::vector<uint8_t> &resp) {
1618 if (ec)
1619 {
1620 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
1621 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001622 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001623 return;
1624 }
1625 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
1626 {"PECIResponse", resp}};
1627 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001628 // Call the SendRawPECI command with the provided data
1629 crow::connections::systemBus->async_method_call(
Jason M. Bills424c4172019-03-21 13:50:33 -07001630 std::move(sendRawPECICallback), CrashdumpObject, CrashdumpPath,
1631 CrashdumpRawPECIInterface, "SendRawPeci", clientAddress, readLength,
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001632 peciCommand);
Ed Tanous1da66f72018-07-27 16:13:37 -07001633 }
1634};
1635
Andrew Geisslercb92c032018-08-17 07:56:14 -07001636/**
1637 * DBusLogServiceActionsClear class supports POST method for ClearLog action.
1638 */
1639class DBusLogServiceActionsClear : public Node
1640{
1641 public:
1642 DBusLogServiceActionsClear(CrowApp &app) :
1643 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
1644 "LogService.Reset")
1645 {
1646 entityPrivileges = {
1647 {boost::beast::http::verb::get, {{"Login"}}},
1648 {boost::beast::http::verb::head, {{"Login"}}},
1649 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1650 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1651 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1652 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1653 }
1654
1655 private:
1656 /**
1657 * Function handles POST method request.
1658 * The Clear Log actions does not require any parameter.The action deletes
1659 * all entries found in the Entries collection for this Log Service.
1660 */
1661 void doPost(crow::Response &res, const crow::Request &req,
1662 const std::vector<std::string> &params) override
1663 {
1664 BMCWEB_LOG_DEBUG << "Do delete all entries.";
1665
1666 auto asyncResp = std::make_shared<AsyncResp>(res);
1667 // Process response from Logging service.
1668 auto resp_handler = [asyncResp](const boost::system::error_code ec) {
1669 BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done";
1670 if (ec)
1671 {
1672 // TODO Handle for specific error code
1673 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec;
1674 asyncResp->res.result(
1675 boost::beast::http::status::internal_server_error);
1676 return;
1677 }
1678
1679 asyncResp->res.result(boost::beast::http::status::no_content);
1680 };
1681
1682 // Make call to Logging service to request Clear Log
1683 crow::connections::systemBus->async_method_call(
1684 resp_handler, "xyz.openbmc_project.Logging",
1685 "/xyz/openbmc_project/logging",
1686 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
1687 }
1688};
Ed Tanous1da66f72018-07-27 16:13:37 -07001689} // namespace redfish