blob: 0f4a90226f1538807ac4096c191ded66d95429b5 [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. Bills5b61b5e2019-10-16 10:59:02 -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 *deleteAllInterface =
41 "xyz.openbmc_project.Collection.DeleteAll";
42constexpr char const *crashdumpOnDemandInterface =
Jason M. Bills424c4172019-03-21 13:50:33 -070043 "com.intel.crashdump.OnDemand";
Jason M. Bills5b61b5e2019-10-16 10:59:02 -070044constexpr char const *crashdumpRawPECIInterface =
Jason M. Bills424c4172019-03-21 13:50:33 -070045 "com.intel.crashdump.SendRawPeci";
Ed Tanous1da66f72018-07-27 16:13:37 -070046
Jason M. Bills4851d452019-03-28 11:27:48 -070047namespace message_registries
48{
49static const Message *getMessageFromRegistry(
50 const std::string &messageKey,
51 const boost::beast::span<const MessageEntry> registry)
52{
53 boost::beast::span<const MessageEntry>::const_iterator messageIt =
54 std::find_if(registry.cbegin(), registry.cend(),
55 [&messageKey](const MessageEntry &messageEntry) {
56 return !std::strcmp(messageEntry.first,
57 messageKey.c_str());
58 });
59 if (messageIt != registry.cend())
60 {
61 return &messageIt->second;
62 }
63
64 return nullptr;
65}
66
67static const Message *getMessage(const std::string_view &messageID)
68{
69 // Redfish MessageIds are in the form
70 // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find
71 // the right Message
72 std::vector<std::string> fields;
73 fields.reserve(4);
74 boost::split(fields, messageID, boost::is_any_of("."));
75 std::string &registryName = fields[0];
76 std::string &messageKey = fields[3];
77
78 // Find the right registry and check it for the MessageKey
79 if (std::string(base::header.registryPrefix) == registryName)
80 {
81 return getMessageFromRegistry(
82 messageKey, boost::beast::span<const MessageEntry>(base::registry));
83 }
84 if (std::string(openbmc::header.registryPrefix) == registryName)
85 {
86 return getMessageFromRegistry(
87 messageKey,
88 boost::beast::span<const MessageEntry>(openbmc::registry));
89 }
90 return nullptr;
91}
92} // namespace message_registries
93
James Feistf6150402019-01-08 10:36:20 -080094namespace fs = std::filesystem;
Ed Tanous1da66f72018-07-27 16:13:37 -070095
Andrew Geisslercb92c032018-08-17 07:56:14 -070096using GetManagedPropertyType = boost::container::flat_map<
97 std::string,
98 sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t,
99 int32_t, uint32_t, int64_t, uint64_t, double>>;
100
101using GetManagedObjectsType = boost::container::flat_map<
102 sdbusplus::message::object_path,
103 boost::container::flat_map<std::string, GetManagedPropertyType>>;
104
105inline std::string translateSeverityDbusToRedfish(const std::string &s)
106{
107 if (s == "xyz.openbmc_project.Logging.Entry.Level.Alert")
108 {
109 return "Critical";
110 }
111 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Critical")
112 {
113 return "Critical";
114 }
115 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Debug")
116 {
117 return "OK";
118 }
119 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency")
120 {
121 return "Critical";
122 }
123 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Error")
124 {
125 return "Critical";
126 }
127 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Informational")
128 {
129 return "OK";
130 }
131 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Notice")
132 {
133 return "OK";
134 }
135 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
136 {
137 return "Warning";
138 }
139 return "";
140}
141
Jason M. Bills16428a12018-11-02 12:42:29 -0700142static int getJournalMetadata(sd_journal *journal,
Ed Tanous39e77502019-03-04 17:35:53 -0800143 const std::string_view &field,
144 std::string_view &contents)
Jason M. Bills16428a12018-11-02 12:42:29 -0700145{
146 const char *data = nullptr;
147 size_t length = 0;
148 int ret = 0;
149 // Get the metadata from the requested field of the journal entry
Ed Tanous271584a2019-07-09 16:24:22 -0700150 ret = sd_journal_get_data(journal, field.data(),
151 reinterpret_cast<const void **>(&data), &length);
Jason M. Bills16428a12018-11-02 12:42:29 -0700152 if (ret < 0)
153 {
154 return ret;
155 }
Ed Tanous39e77502019-03-04 17:35:53 -0800156 contents = std::string_view(data, length);
Jason M. Bills16428a12018-11-02 12:42:29 -0700157 // Only use the content after the "=" character.
158 contents.remove_prefix(std::min(contents.find("=") + 1, contents.size()));
159 return ret;
160}
161
162static int getJournalMetadata(sd_journal *journal,
Ed Tanous39e77502019-03-04 17:35:53 -0800163 const std::string_view &field, const int &base,
Ed Tanous271584a2019-07-09 16:24:22 -0700164 long int &contents)
Jason M. Bills16428a12018-11-02 12:42:29 -0700165{
166 int ret = 0;
Ed Tanous39e77502019-03-04 17:35:53 -0800167 std::string_view metadata;
Jason M. Bills16428a12018-11-02 12:42:29 -0700168 // Get the metadata from the requested field of the journal entry
169 ret = getJournalMetadata(journal, field, metadata);
170 if (ret < 0)
171 {
172 return ret;
173 }
Ed Tanousb01bf292019-03-25 19:25:26 +0000174 contents = strtol(metadata.data(), nullptr, base);
Jason M. Bills16428a12018-11-02 12:42:29 -0700175 return ret;
176}
177
178static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp)
179{
180 int ret = 0;
181 uint64_t timestamp = 0;
182 ret = sd_journal_get_realtime_usec(journal, &timestamp);
183 if (ret < 0)
184 {
185 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
186 << strerror(-ret);
187 return false;
188 }
189 time_t t =
190 static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s
191 struct tm *loctime = localtime(&t);
192 char entryTime[64] = {};
Ed Tanous99131cd2019-10-24 11:12:47 -0700193 if (nullptr != loctime)
Jason M. Bills16428a12018-11-02 12:42:29 -0700194 {
195 strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime);
196 }
197 // Insert the ':' into the timezone
Ed Tanous39e77502019-03-04 17:35:53 -0800198 std::string_view t1(entryTime);
199 std::string_view t2(entryTime);
Jason M. Bills16428a12018-11-02 12:42:29 -0700200 if (t1.size() > 2 && t2.size() > 2)
201 {
202 t1.remove_suffix(2);
203 t2.remove_prefix(t2.size() - 2);
204 }
Ed Tanous39e77502019-03-04 17:35:53 -0800205 entryTimestamp = std::string(t1) + ":" + std::string(t2);
Jason M. Bills16428a12018-11-02 12:42:29 -0700206 return true;
207}
208
209static bool getSkipParam(crow::Response &res, const crow::Request &req,
Ed Tanous271584a2019-07-09 16:24:22 -0700210 uint64_t &skip)
Jason M. Bills16428a12018-11-02 12:42:29 -0700211{
212 char *skipParam = req.urlParams.get("$skip");
213 if (skipParam != nullptr)
214 {
215 char *ptr = nullptr;
Ed Tanous271584a2019-07-09 16:24:22 -0700216 skip = std::strtoul(skipParam, &ptr, 10);
Jason M. Bills16428a12018-11-02 12:42:29 -0700217 if (*skipParam == '\0' || *ptr != '\0')
218 {
219
220 messages::queryParameterValueTypeError(res, std::string(skipParam),
221 "$skip");
222 return false;
223 }
Jason M. Bills16428a12018-11-02 12:42:29 -0700224 }
225 return true;
226}
227
Ed Tanous271584a2019-07-09 16:24:22 -0700228static constexpr const uint64_t maxEntriesPerPage = 1000;
Jason M. Bills16428a12018-11-02 12:42:29 -0700229static bool getTopParam(crow::Response &res, const crow::Request &req,
Ed Tanous271584a2019-07-09 16:24:22 -0700230 uint64_t &top)
Jason M. Bills16428a12018-11-02 12:42:29 -0700231{
232 char *topParam = req.urlParams.get("$top");
233 if (topParam != nullptr)
234 {
235 char *ptr = nullptr;
Ed Tanous271584a2019-07-09 16:24:22 -0700236 top = std::strtoul(topParam, &ptr, 10);
Jason M. Bills16428a12018-11-02 12:42:29 -0700237 if (*topParam == '\0' || *ptr != '\0')
238 {
239 messages::queryParameterValueTypeError(res, std::string(topParam),
240 "$top");
241 return false;
242 }
Ed Tanous271584a2019-07-09 16:24:22 -0700243 if (top < 1U || top > maxEntriesPerPage)
Jason M. Bills16428a12018-11-02 12:42:29 -0700244 {
245
246 messages::queryParameterOutOfRange(
247 res, std::to_string(top), "$top",
248 "1-" + std::to_string(maxEntriesPerPage));
249 return false;
250 }
251 }
252 return true;
253}
254
Jason M. Billse85d6b12019-07-29 17:01:15 -0700255static bool getUniqueEntryID(sd_journal *journal, std::string &entryID,
256 const bool firstEntry = true)
Jason M. Bills16428a12018-11-02 12:42:29 -0700257{
258 int ret = 0;
259 static uint64_t prevTs = 0;
260 static int index = 0;
Jason M. Billse85d6b12019-07-29 17:01:15 -0700261 if (firstEntry)
262 {
263 prevTs = 0;
264 }
265
Jason M. Bills16428a12018-11-02 12:42:29 -0700266 // Get the entry timestamp
267 uint64_t curTs = 0;
268 ret = sd_journal_get_realtime_usec(journal, &curTs);
269 if (ret < 0)
270 {
271 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
272 << strerror(-ret);
273 return false;
274 }
275 // If the timestamp isn't unique, increment the index
276 if (curTs == prevTs)
277 {
278 index++;
279 }
280 else
281 {
282 // Otherwise, reset it
283 index = 0;
284 }
285 // Save the timestamp
286 prevTs = curTs;
287
288 entryID = std::to_string(curTs);
289 if (index > 0)
290 {
291 entryID += "_" + std::to_string(index);
292 }
293 return true;
294}
295
Jason M. Billse85d6b12019-07-29 17:01:15 -0700296static bool getUniqueEntryID(const std::string &logEntry, std::string &entryID,
297 const bool firstEntry = true)
Jason M. Bills95820182019-04-22 16:25:34 -0700298{
Ed Tanous271584a2019-07-09 16:24:22 -0700299 static time_t prevTs = 0;
Jason M. Bills95820182019-04-22 16:25:34 -0700300 static int index = 0;
Jason M. Billse85d6b12019-07-29 17:01:15 -0700301 if (firstEntry)
302 {
303 prevTs = 0;
304 }
305
Jason M. Bills95820182019-04-22 16:25:34 -0700306 // Get the entry timestamp
Ed Tanous271584a2019-07-09 16:24:22 -0700307 std::time_t curTs = 0;
Jason M. Bills95820182019-04-22 16:25:34 -0700308 std::tm timeStruct = {};
309 std::istringstream entryStream(logEntry);
310 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
311 {
312 curTs = std::mktime(&timeStruct);
313 }
314 // If the timestamp isn't unique, increment the index
315 if (curTs == prevTs)
316 {
317 index++;
318 }
319 else
320 {
321 // Otherwise, reset it
322 index = 0;
323 }
324 // Save the timestamp
325 prevTs = curTs;
326
327 entryID = std::to_string(curTs);
328 if (index > 0)
329 {
330 entryID += "_" + std::to_string(index);
331 }
332 return true;
333}
334
Jason M. Bills16428a12018-11-02 12:42:29 -0700335static bool getTimestampFromID(crow::Response &res, const std::string &entryID,
Ed Tanous271584a2019-07-09 16:24:22 -0700336 uint64_t &timestamp, uint64_t &index)
Jason M. Bills16428a12018-11-02 12:42:29 -0700337{
338 if (entryID.empty())
339 {
340 return false;
341 }
342 // Convert the unique ID back to a timestamp to find the entry
Ed Tanous39e77502019-03-04 17:35:53 -0800343 std::string_view tsStr(entryID);
Jason M. Bills16428a12018-11-02 12:42:29 -0700344
345 auto underscorePos = tsStr.find("_");
346 if (underscorePos != tsStr.npos)
347 {
348 // Timestamp has an index
349 tsStr.remove_suffix(tsStr.size() - underscorePos);
Ed Tanous39e77502019-03-04 17:35:53 -0800350 std::string_view indexStr(entryID);
Jason M. Bills16428a12018-11-02 12:42:29 -0700351 indexStr.remove_prefix(underscorePos + 1);
352 std::size_t pos;
353 try
354 {
Ed Tanous39e77502019-03-04 17:35:53 -0800355 index = std::stoul(std::string(indexStr), &pos);
Jason M. Bills16428a12018-11-02 12:42:29 -0700356 }
Ed Tanous271584a2019-07-09 16:24:22 -0700357 catch (std::invalid_argument &)
Jason M. Bills16428a12018-11-02 12:42:29 -0700358 {
359 messages::resourceMissingAtURI(res, entryID);
360 return false;
361 }
Ed Tanous271584a2019-07-09 16:24:22 -0700362 catch (std::out_of_range &)
Jason M. Bills16428a12018-11-02 12:42:29 -0700363 {
364 messages::resourceMissingAtURI(res, entryID);
365 return false;
366 }
367 if (pos != indexStr.size())
368 {
369 messages::resourceMissingAtURI(res, entryID);
370 return false;
371 }
372 }
373 // Timestamp has no index
374 std::size_t pos;
375 try
376 {
Ed Tanous39e77502019-03-04 17:35:53 -0800377 timestamp = std::stoull(std::string(tsStr), &pos);
Jason M. Bills16428a12018-11-02 12:42:29 -0700378 }
Ed Tanous271584a2019-07-09 16:24:22 -0700379 catch (std::invalid_argument &)
Jason M. Bills16428a12018-11-02 12:42:29 -0700380 {
381 messages::resourceMissingAtURI(res, entryID);
382 return false;
383 }
Ed Tanous271584a2019-07-09 16:24:22 -0700384 catch (std::out_of_range &)
Jason M. Bills16428a12018-11-02 12:42:29 -0700385 {
386 messages::resourceMissingAtURI(res, entryID);
387 return false;
388 }
389 if (pos != tsStr.size())
390 {
391 messages::resourceMissingAtURI(res, entryID);
392 return false;
393 }
394 return true;
395}
396
Jason M. Bills95820182019-04-22 16:25:34 -0700397static bool
398 getRedfishLogFiles(std::vector<std::filesystem::path> &redfishLogFiles)
399{
400 static const std::filesystem::path redfishLogDir = "/var/log";
401 static const std::string redfishLogFilename = "redfish";
402
403 // Loop through the directory looking for redfish log files
404 for (const std::filesystem::directory_entry &dirEnt :
405 std::filesystem::directory_iterator(redfishLogDir))
406 {
407 // If we find a redfish log file, save the path
408 std::string filename = dirEnt.path().filename();
409 if (boost::starts_with(filename, redfishLogFilename))
410 {
411 redfishLogFiles.emplace_back(redfishLogDir / filename);
412 }
413 }
414 // As the log files rotate, they are appended with a ".#" that is higher for
415 // the older logs. Since we don't expect more than 10 log files, we
416 // can just sort the list to get them in order from newest to oldest
417 std::sort(redfishLogFiles.begin(), redfishLogFiles.end());
418
419 return !redfishLogFiles.empty();
420}
421
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800422class SystemLogServiceCollection : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700423{
424 public:
425 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800426 SystemLogServiceCollection(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800427 Node(app, "/redfish/v1/Systems/system/LogServices/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800428 {
429 entityPrivileges = {
430 {boost::beast::http::verb::get, {{"Login"}}},
431 {boost::beast::http::verb::head, {{"Login"}}},
432 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
433 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
434 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
435 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
436 }
437
438 private:
439 /**
440 * Functions triggers appropriate requests on DBus
441 */
442 void doGet(crow::Response &res, const crow::Request &req,
443 const std::vector<std::string> &params) override
444 {
445 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800446 // Collections don't include the static data added by SubRoute because
447 // it has a duplicate entry for members
448 asyncResp->res.jsonValue["@odata.type"] =
449 "#LogServiceCollection.LogServiceCollection";
450 asyncResp->res.jsonValue["@odata.context"] =
451 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
452 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800453 "/redfish/v1/Systems/system/LogServices";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800454 asyncResp->res.jsonValue["Name"] = "System Log Services Collection";
455 asyncResp->res.jsonValue["Description"] =
456 "Collection of LogServices for this Computer System";
457 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
458 logServiceArray = nlohmann::json::array();
Ed Tanous029573d2019-02-01 10:57:49 -0800459 logServiceArray.push_back(
460 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}});
Jason M. Billsd53dd412019-02-12 17:16:22 -0800461#ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
462 logServiceArray.push_back(
Anthony Wilson08a4e4b2019-04-12 08:23:05 -0500463 {{"@odata.id",
464 "/redfish/v1/Systems/system/LogServices/Crashdump"}});
Jason M. Billsd53dd412019-02-12 17:16:22 -0800465#endif
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800466 asyncResp->res.jsonValue["Members@odata.count"] =
467 logServiceArray.size();
468 }
469};
470
471class EventLogService : public Node
472{
473 public:
474 template <typename CrowApp>
475 EventLogService(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800476 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800477 {
478 entityPrivileges = {
479 {boost::beast::http::verb::get, {{"Login"}}},
480 {boost::beast::http::verb::head, {{"Login"}}},
481 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
482 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
483 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
484 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
485 }
486
487 private:
488 void doGet(crow::Response &res, const crow::Request &req,
489 const std::vector<std::string> &params) override
490 {
491 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
492
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800493 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800494 "/redfish/v1/Systems/system/LogServices/EventLog";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800495 asyncResp->res.jsonValue["@odata.type"] =
496 "#LogService.v1_1_0.LogService";
497 asyncResp->res.jsonValue["@odata.context"] =
498 "/redfish/v1/$metadata#LogService.LogService";
499 asyncResp->res.jsonValue["Name"] = "Event Log Service";
500 asyncResp->res.jsonValue["Description"] = "System Event Log Service";
501 asyncResp->res.jsonValue["Id"] = "Event Log";
502 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
503 asyncResp->res.jsonValue["Entries"] = {
504 {"@odata.id",
Ed Tanous029573d2019-02-01 10:57:49 -0800505 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}};
Gunnar Millse7d6c8b2019-07-03 11:30:01 -0500506 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = {
507
508 {"target", "/redfish/v1/Systems/system/LogServices/EventLog/"
509 "Actions/LogService.ClearLog"}};
Jason M. Bills489640c2019-05-17 09:56:36 -0700510 }
511};
512
Tim Lee1f56a3a2019-10-09 10:17:57 +0800513class JournalEventLogClear : public Node
Jason M. Bills489640c2019-05-17 09:56:36 -0700514{
515 public:
Tim Lee1f56a3a2019-10-09 10:17:57 +0800516 JournalEventLogClear(CrowApp &app) :
Jason M. Bills489640c2019-05-17 09:56:36 -0700517 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
518 "LogService.ClearLog/")
519 {
520 entityPrivileges = {
521 {boost::beast::http::verb::get, {{"Login"}}},
522 {boost::beast::http::verb::head, {{"Login"}}},
523 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
524 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
525 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
526 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
527 }
528
529 private:
530 void doPost(crow::Response &res, const crow::Request &req,
531 const std::vector<std::string> &params) override
532 {
533 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
534
535 // Clear the EventLog by deleting the log files
536 std::vector<std::filesystem::path> redfishLogFiles;
537 if (getRedfishLogFiles(redfishLogFiles))
538 {
539 for (const std::filesystem::path &file : redfishLogFiles)
540 {
541 std::error_code ec;
542 std::filesystem::remove(file, ec);
543 }
544 }
545
546 // Reload rsyslog so it knows to start new log files
547 crow::connections::systemBus->async_method_call(
548 [asyncResp](const boost::system::error_code ec) {
549 if (ec)
550 {
551 BMCWEB_LOG_ERROR << "Failed to reload rsyslog: " << ec;
552 messages::internalError(asyncResp->res);
553 return;
554 }
555
556 messages::success(asyncResp->res);
557 },
558 "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
559 "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service",
560 "replace");
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800561 }
562};
563
Jason M. Bills95820182019-04-22 16:25:34 -0700564static int fillEventLogEntryJson(const std::string &logEntryID,
565 const std::string logEntry,
566 nlohmann::json &logEntryJson)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800567{
Jason M. Bills95820182019-04-22 16:25:34 -0700568 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
Jason M. Billscd225da2019-05-08 15:31:57 -0700569 // First get the Timestamp
570 size_t space = logEntry.find_first_of(" ");
571 if (space == std::string::npos)
Jason M. Bills95820182019-04-22 16:25:34 -0700572 {
573 return 1;
574 }
Jason M. Billscd225da2019-05-08 15:31:57 -0700575 std::string timestamp = logEntry.substr(0, space);
576 // Then get the log contents
577 size_t entryStart = logEntry.find_first_not_of(" ", space);
578 if (entryStart == std::string::npos)
579 {
580 return 1;
581 }
582 std::string_view entry(logEntry);
583 entry.remove_prefix(entryStart);
584 // Use split to separate the entry into its fields
585 std::vector<std::string> logEntryFields;
586 boost::split(logEntryFields, entry, boost::is_any_of(","),
587 boost::token_compress_on);
588 // We need at least a MessageId to be valid
589 if (logEntryFields.size() < 1)
590 {
591 return 1;
592 }
593 std::string &messageID = logEntryFields[0];
Jason M. Bills95820182019-04-22 16:25:34 -0700594
Jason M. Bills4851d452019-03-28 11:27:48 -0700595 // Get the Message from the MessageRegistry
596 const message_registries::Message *message =
597 message_registries::getMessage(messageID);
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800598
Jason M. Bills4851d452019-03-28 11:27:48 -0700599 std::string msg;
600 std::string severity;
601 if (message != nullptr)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800602 {
Jason M. Bills4851d452019-03-28 11:27:48 -0700603 msg = message->message;
604 severity = message->severity;
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800605 }
606
Jason M. Bills15a86ff2019-06-18 13:49:54 -0700607 // Get the MessageArgs from the log if there are any
608 boost::beast::span<std::string> messageArgs;
609 if (logEntryFields.size() > 1)
Jason M. Bills4851d452019-03-28 11:27:48 -0700610 {
Jason M. Bills15a86ff2019-06-18 13:49:54 -0700611 std::string &messageArgsStart = logEntryFields[1];
612 // If the first string is empty, assume there are no MessageArgs
613 std::size_t messageArgsSize = 0;
614 if (!messageArgsStart.empty())
Jason M. Bills4851d452019-03-28 11:27:48 -0700615 {
Jason M. Bills15a86ff2019-06-18 13:49:54 -0700616 messageArgsSize = logEntryFields.size() - 1;
617 }
618
619 messageArgs = boost::beast::span(&messageArgsStart, messageArgsSize);
620
621 // Fill the MessageArgs into the Message
622 int i = 0;
623 for (const std::string &messageArg : messageArgs)
624 {
625 std::string argStr = "%" + std::to_string(++i);
626 size_t argPos = msg.find(argStr);
627 if (argPos != std::string::npos)
628 {
629 msg.replace(argPos, argStr.length(), messageArg);
630 }
Jason M. Bills4851d452019-03-28 11:27:48 -0700631 }
632 }
633
Jason M. Bills95820182019-04-22 16:25:34 -0700634 // Get the Created time from the timestamp. The log timestamp is in RFC3339
635 // format which matches the Redfish format except for the fractional seconds
636 // between the '.' and the '+', so just remove them.
637 std::size_t dot = timestamp.find_first_of(".");
638 std::size_t plus = timestamp.find_first_of("+");
639 if (dot != std::string::npos && plus != std::string::npos)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800640 {
Jason M. Bills95820182019-04-22 16:25:34 -0700641 timestamp.erase(dot, plus - dot);
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800642 }
643
644 // Fill in the log entry with the gathered data
Jason M. Bills95820182019-04-22 16:25:34 -0700645 logEntryJson = {
Andrew Geisslercb92c032018-08-17 07:56:14 -0700646 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800647 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Ed Tanous029573d2019-02-01 10:57:49 -0800648 {"@odata.id",
Jason M. Bills897967d2019-07-29 17:05:30 -0700649 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
Jason M. Bills95820182019-04-22 16:25:34 -0700650 logEntryID},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800651 {"Name", "System Event Log Entry"},
Jason M. Bills95820182019-04-22 16:25:34 -0700652 {"Id", logEntryID},
653 {"Message", std::move(msg)},
654 {"MessageId", std::move(messageID)},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800655 {"MessageArgs", std::move(messageArgs)},
656 {"EntryType", "Event"},
Jason M. Bills95820182019-04-22 16:25:34 -0700657 {"Severity", std::move(severity)},
658 {"Created", std::move(timestamp)}};
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800659 return 0;
660}
661
Anthony Wilson27062602019-04-22 02:10:09 -0500662class JournalEventLogEntryCollection : public Node
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800663{
664 public:
665 template <typename CrowApp>
Anthony Wilson27062602019-04-22 02:10:09 -0500666 JournalEventLogEntryCollection(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800667 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800668 {
669 entityPrivileges = {
670 {boost::beast::http::verb::get, {{"Login"}}},
671 {boost::beast::http::verb::head, {{"Login"}}},
672 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
673 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
674 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
675 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
676 }
677
678 private:
679 void doGet(crow::Response &res, const crow::Request &req,
680 const std::vector<std::string> &params) override
681 {
682 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous271584a2019-07-09 16:24:22 -0700683 uint64_t skip = 0;
684 uint64_t top = maxEntriesPerPage; // Show max entries by default
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800685 if (!getSkipParam(asyncResp->res, req, skip))
686 {
687 return;
688 }
689 if (!getTopParam(asyncResp->res, req, top))
690 {
691 return;
692 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800693 // Collections don't include the static data added by SubRoute because
694 // it has a duplicate entry for members
695 asyncResp->res.jsonValue["@odata.type"] =
696 "#LogEntryCollection.LogEntryCollection";
697 asyncResp->res.jsonValue["@odata.context"] =
698 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
699 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800700 "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800701 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
702 asyncResp->res.jsonValue["Description"] =
703 "Collection of System Event Log Entries";
Andrew Geisslercb92c032018-08-17 07:56:14 -0700704
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800705 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
706 logEntryArray = nlohmann::json::array();
Jason M. Bills95820182019-04-22 16:25:34 -0700707 // Go through the log files and create a unique ID for each entry
708 std::vector<std::filesystem::path> redfishLogFiles;
709 getRedfishLogFiles(redfishLogFiles);
Ed Tanousb01bf292019-03-25 19:25:26 +0000710 uint64_t entryCount = 0;
Jason M. Billscd225da2019-05-08 15:31:57 -0700711 std::string logEntry;
Jason M. Bills95820182019-04-22 16:25:34 -0700712
713 // Oldest logs are in the last file, so start there and loop backwards
Jason M. Billscd225da2019-05-08 15:31:57 -0700714 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend();
715 it++)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800716 {
Jason M. Billscd225da2019-05-08 15:31:57 -0700717 std::ifstream logStream(*it);
Jason M. Bills95820182019-04-22 16:25:34 -0700718 if (!logStream.is_open())
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800719 {
720 continue;
721 }
722
Jason M. Billse85d6b12019-07-29 17:01:15 -0700723 // Reset the unique ID on the first entry
724 bool firstEntry = true;
Jason M. Bills95820182019-04-22 16:25:34 -0700725 while (std::getline(logStream, logEntry))
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800726 {
Jason M. Bills95820182019-04-22 16:25:34 -0700727 entryCount++;
728 // Handle paging using skip (number of entries to skip from the
729 // start) and top (number of entries to display)
730 if (entryCount <= skip || entryCount > skip + top)
731 {
732 continue;
733 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800734
Jason M. Bills95820182019-04-22 16:25:34 -0700735 std::string idStr;
Jason M. Billse85d6b12019-07-29 17:01:15 -0700736 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
Jason M. Bills95820182019-04-22 16:25:34 -0700737 {
738 continue;
739 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800740
Jason M. Billse85d6b12019-07-29 17:01:15 -0700741 if (firstEntry)
742 {
743 firstEntry = false;
744 }
745
Jason M. Bills95820182019-04-22 16:25:34 -0700746 logEntryArray.push_back({});
747 nlohmann::json &bmcLogEntry = logEntryArray.back();
748 if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0)
749 {
750 messages::internalError(asyncResp->res);
751 return;
752 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800753 }
754 }
755 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
756 if (skip + top < entryCount)
757 {
758 asyncResp->res.jsonValue["Members@odata.nextLink"] =
Jason M. Bills95820182019-04-22 16:25:34 -0700759 "/redfish/v1/Systems/system/LogServices/EventLog/"
760 "Entries?$skip=" +
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800761 std::to_string(skip + top);
762 }
Anthony Wilson08a4e4b2019-04-12 08:23:05 -0500763 }
764};
765
Jason M. Bills897967d2019-07-29 17:05:30 -0700766class JournalEventLogEntry : public Node
767{
768 public:
769 JournalEventLogEntry(CrowApp &app) :
770 Node(app,
771 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/",
772 std::string())
773 {
774 entityPrivileges = {
775 {boost::beast::http::verb::get, {{"Login"}}},
776 {boost::beast::http::verb::head, {{"Login"}}},
777 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
778 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
779 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
780 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
781 }
782
783 private:
784 void doGet(crow::Response &res, const crow::Request &req,
785 const std::vector<std::string> &params) override
786 {
787 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
788 if (params.size() != 1)
789 {
790 messages::internalError(asyncResp->res);
791 return;
792 }
793 const std::string &targetID = params[0];
794
795 // Go through the log files and check the unique ID for each entry to
796 // find the target entry
797 std::vector<std::filesystem::path> redfishLogFiles;
798 getRedfishLogFiles(redfishLogFiles);
799 std::string logEntry;
800
801 // Oldest logs are in the last file, so start there and loop backwards
802 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend();
803 it++)
804 {
805 std::ifstream logStream(*it);
806 if (!logStream.is_open())
807 {
808 continue;
809 }
810
811 // Reset the unique ID on the first entry
812 bool firstEntry = true;
813 while (std::getline(logStream, logEntry))
814 {
815 std::string idStr;
816 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
817 {
818 continue;
819 }
820
821 if (firstEntry)
822 {
823 firstEntry = false;
824 }
825
826 if (idStr == targetID)
827 {
828 if (fillEventLogEntryJson(idStr, logEntry,
829 asyncResp->res.jsonValue) != 0)
830 {
831 messages::internalError(asyncResp->res);
832 return;
833 }
834 return;
835 }
836 }
837 }
838 // Requested ID was not found
839 messages::resourceMissingAtURI(asyncResp->res, targetID);
840 }
841};
842
Anthony Wilson08a4e4b2019-04-12 08:23:05 -0500843class DBusEventLogEntryCollection : public Node
844{
845 public:
846 template <typename CrowApp>
847 DBusEventLogEntryCollection(CrowApp &app) :
848 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
849 {
850 entityPrivileges = {
851 {boost::beast::http::verb::get, {{"Login"}}},
852 {boost::beast::http::verb::head, {{"Login"}}},
853 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
854 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
855 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
856 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
857 }
858
859 private:
860 void doGet(crow::Response &res, const crow::Request &req,
861 const std::vector<std::string> &params) override
862 {
863 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
864
865 // Collections don't include the static data added by SubRoute because
866 // it has a duplicate entry for members
867 asyncResp->res.jsonValue["@odata.type"] =
868 "#LogEntryCollection.LogEntryCollection";
869 asyncResp->res.jsonValue["@odata.context"] =
870 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
871 asyncResp->res.jsonValue["@odata.id"] =
872 "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
873 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
874 asyncResp->res.jsonValue["Description"] =
875 "Collection of System Event Log Entries";
876
Andrew Geisslercb92c032018-08-17 07:56:14 -0700877 // DBus implementation of EventLog/Entries
878 // Make call to Logging Service to find all log entry objects
879 crow::connections::systemBus->async_method_call(
880 [asyncResp](const boost::system::error_code ec,
881 GetManagedObjectsType &resp) {
882 if (ec)
883 {
884 // TODO Handle for specific error code
885 BMCWEB_LOG_ERROR
886 << "getLogEntriesIfaceData resp_handler got error "
887 << ec;
888 messages::internalError(asyncResp->res);
889 return;
890 }
891 nlohmann::json &entriesArray =
892 asyncResp->res.jsonValue["Members"];
893 entriesArray = nlohmann::json::array();
894 for (auto &objectPath : resp)
895 {
896 for (auto &interfaceMap : objectPath.second)
897 {
898 if (interfaceMap.first !=
899 "xyz.openbmc_project.Logging.Entry")
900 {
901 BMCWEB_LOG_DEBUG << "Bailing early on "
902 << interfaceMap.first;
903 continue;
904 }
905 entriesArray.push_back({});
906 nlohmann::json &thisEntry = entriesArray.back();
Ed Tanous66664f22019-10-11 13:05:49 -0700907 uint32_t *id = nullptr;
908 std::time_t timestamp{};
909 std::string *severity = nullptr;
910 std::string *message = nullptr;
Andrew Geisslercb92c032018-08-17 07:56:14 -0700911 for (auto &propertyMap : interfaceMap.second)
912 {
913 if (propertyMap.first == "Id")
914 {
915 id = sdbusplus::message::variant_ns::get_if<
916 uint32_t>(&propertyMap.second);
917 if (id == nullptr)
918 {
919 messages::propertyMissing(asyncResp->res,
920 "Id");
921 }
922 }
923 else if (propertyMap.first == "Timestamp")
924 {
925 const uint64_t *millisTimeStamp =
926 std::get_if<uint64_t>(&propertyMap.second);
927 if (millisTimeStamp == nullptr)
928 {
929 messages::propertyMissing(asyncResp->res,
930 "Timestamp");
Ed Tanous271584a2019-07-09 16:24:22 -0700931 continue;
Andrew Geisslercb92c032018-08-17 07:56:14 -0700932 }
933 // Retrieve Created property with format:
934 // yyyy-mm-ddThh:mm:ss
935 std::chrono::milliseconds chronoTimeStamp(
936 *millisTimeStamp);
Ed Tanous271584a2019-07-09 16:24:22 -0700937 timestamp = std::chrono::duration_cast<
938 std::chrono::duration<int>>(
939 chronoTimeStamp)
940 .count();
Andrew Geisslercb92c032018-08-17 07:56:14 -0700941 }
942 else if (propertyMap.first == "Severity")
943 {
944 severity = std::get_if<std::string>(
945 &propertyMap.second);
946 if (severity == nullptr)
947 {
948 messages::propertyMissing(asyncResp->res,
949 "Severity");
950 }
951 }
952 else if (propertyMap.first == "Message")
953 {
954 message = std::get_if<std::string>(
955 &propertyMap.second);
956 if (message == nullptr)
957 {
958 messages::propertyMissing(asyncResp->res,
959 "Message");
960 }
961 }
962 }
963 thisEntry = {
964 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
965 {"@odata.context", "/redfish/v1/"
966 "$metadata#LogEntry.LogEntry"},
967 {"@odata.id",
968 "/redfish/v1/Systems/system/LogServices/EventLog/"
969 "Entries/" +
970 std::to_string(*id)},
Anthony Wilson27062602019-04-22 02:10:09 -0500971 {"Name", "System Event Log Entry"},
Andrew Geisslercb92c032018-08-17 07:56:14 -0700972 {"Id", std::to_string(*id)},
973 {"Message", *message},
974 {"EntryType", "Event"},
975 {"Severity",
976 translateSeverityDbusToRedfish(*severity)},
977 {"Created", crow::utility::getDateTime(timestamp)}};
978 }
979 }
980 std::sort(entriesArray.begin(), entriesArray.end(),
981 [](const nlohmann::json &left,
982 const nlohmann::json &right) {
983 return (left["Id"] <= right["Id"]);
984 });
985 asyncResp->res.jsonValue["Members@odata.count"] =
986 entriesArray.size();
987 },
988 "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging",
989 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800990 }
991};
992
Anthony Wilson08a4e4b2019-04-12 08:23:05 -0500993class DBusEventLogEntry : public Node
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800994{
995 public:
Anthony Wilson08a4e4b2019-04-12 08:23:05 -0500996 DBusEventLogEntry(CrowApp &app) :
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800997 Node(app,
Ed Tanous029573d2019-02-01 10:57:49 -0800998 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/",
999 std::string())
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001000 {
1001 entityPrivileges = {
1002 {boost::beast::http::verb::get, {{"Login"}}},
1003 {boost::beast::http::verb::head, {{"Login"}}},
1004 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1005 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1006 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1007 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1008 }
1009
1010 private:
1011 void doGet(crow::Response &res, const crow::Request &req,
1012 const std::vector<std::string> &params) override
1013 {
1014 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous029573d2019-02-01 10:57:49 -08001015 if (params.size() != 1)
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001016 {
1017 messages::internalError(asyncResp->res);
1018 return;
1019 }
Ed Tanous029573d2019-02-01 10:57:49 -08001020 const std::string &entryID = params[0];
Andrew Geisslercb92c032018-08-17 07:56:14 -07001021
Andrew Geisslercb92c032018-08-17 07:56:14 -07001022 // DBus implementation of EventLog/Entries
1023 // Make call to Logging Service to find all log entry objects
1024 crow::connections::systemBus->async_method_call(
1025 [asyncResp, entryID](const boost::system::error_code ec,
1026 GetManagedPropertyType &resp) {
1027 if (ec)
1028 {
1029 BMCWEB_LOG_ERROR
1030 << "EventLogEntry (DBus) resp_handler got error " << ec;
1031 messages::internalError(asyncResp->res);
1032 return;
1033 }
Ed Tanous66664f22019-10-11 13:05:49 -07001034 uint32_t *id = nullptr;
1035 std::time_t timestamp{};
1036 std::string *severity = nullptr;
1037 std::string *message = nullptr;
Andrew Geisslercb92c032018-08-17 07:56:14 -07001038 for (auto &propertyMap : resp)
1039 {
1040 if (propertyMap.first == "Id")
1041 {
1042 id = std::get_if<uint32_t>(&propertyMap.second);
1043 if (id == nullptr)
1044 {
1045 messages::propertyMissing(asyncResp->res, "Id");
1046 }
1047 }
1048 else if (propertyMap.first == "Timestamp")
1049 {
1050 const uint64_t *millisTimeStamp =
1051 std::get_if<uint64_t>(&propertyMap.second);
1052 if (millisTimeStamp == nullptr)
1053 {
1054 messages::propertyMissing(asyncResp->res,
1055 "Timestamp");
Ed Tanous271584a2019-07-09 16:24:22 -07001056 continue;
Andrew Geisslercb92c032018-08-17 07:56:14 -07001057 }
1058 // Retrieve Created property with format:
1059 // yyyy-mm-ddThh:mm:ss
1060 std::chrono::milliseconds chronoTimeStamp(
1061 *millisTimeStamp);
1062 timestamp =
Ed Tanous271584a2019-07-09 16:24:22 -07001063 std::chrono::duration_cast<
1064 std::chrono::duration<int>>(chronoTimeStamp)
Andrew Geisslercb92c032018-08-17 07:56:14 -07001065 .count();
1066 }
1067 else if (propertyMap.first == "Severity")
1068 {
1069 severity =
1070 std::get_if<std::string>(&propertyMap.second);
1071 if (severity == nullptr)
1072 {
1073 messages::propertyMissing(asyncResp->res,
1074 "Severity");
1075 }
1076 }
1077 else if (propertyMap.first == "Message")
1078 {
1079 message = std::get_if<std::string>(&propertyMap.second);
1080 if (message == nullptr)
1081 {
1082 messages::propertyMissing(asyncResp->res,
1083 "Message");
1084 }
1085 }
1086 }
Ed Tanous271584a2019-07-09 16:24:22 -07001087 if (id == nullptr || message == nullptr || severity == nullptr)
1088 {
1089 return;
1090 }
Andrew Geisslercb92c032018-08-17 07:56:14 -07001091 asyncResp->res.jsonValue = {
1092 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1093 {"@odata.context", "/redfish/v1/"
1094 "$metadata#LogEntry.LogEntry"},
1095 {"@odata.id",
1096 "/redfish/v1/Systems/system/LogServices/EventLog/"
1097 "Entries/" +
1098 std::to_string(*id)},
Anthony Wilson27062602019-04-22 02:10:09 -05001099 {"Name", "System Event Log Entry"},
Andrew Geisslercb92c032018-08-17 07:56:14 -07001100 {"Id", std::to_string(*id)},
1101 {"Message", *message},
1102 {"EntryType", "Event"},
1103 {"Severity", translateSeverityDbusToRedfish(*severity)},
Anthony Wilson08a4e4b2019-04-12 08:23:05 -05001104 {"Created", crow::utility::getDateTime(timestamp)}};
Andrew Geisslercb92c032018-08-17 07:56:14 -07001105 },
1106 "xyz.openbmc_project.Logging",
1107 "/xyz/openbmc_project/logging/entry/" + entryID,
1108 "org.freedesktop.DBus.Properties", "GetAll",
1109 "xyz.openbmc_project.Logging.Entry");
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001110 }
Chicago Duan336e96c2019-07-15 14:22:08 +08001111
1112 void doDelete(crow::Response &res, const crow::Request &req,
1113 const std::vector<std::string> &params) override
1114 {
1115
1116 BMCWEB_LOG_DEBUG << "Do delete single event entries.";
1117
1118 auto asyncResp = std::make_shared<AsyncResp>(res);
1119
1120 if (params.size() != 1)
1121 {
1122 messages::internalError(asyncResp->res);
1123 return;
1124 }
1125 std::string entryID = params[0];
1126
1127 dbus::utility::escapePathForDbus(entryID);
1128
1129 // Process response from Logging service.
1130 auto respHandler = [asyncResp](const boost::system::error_code ec) {
1131 BMCWEB_LOG_DEBUG << "EventLogEntry (DBus) doDelete callback: Done";
1132 if (ec)
1133 {
1134 // TODO Handle for specific error code
1135 BMCWEB_LOG_ERROR
1136 << "EventLogEntry (DBus) doDelete respHandler got error "
1137 << ec;
1138 asyncResp->res.result(
1139 boost::beast::http::status::internal_server_error);
1140 return;
1141 }
1142
1143 asyncResp->res.result(boost::beast::http::status::ok);
1144 };
1145
1146 // Make call to Logging service to request Delete Log
1147 crow::connections::systemBus->async_method_call(
1148 respHandler, "xyz.openbmc_project.Logging",
1149 "/xyz/openbmc_project/logging/entry/" + entryID,
1150 "xyz.openbmc_project.Object.Delete", "Delete");
1151 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001152};
1153
1154class BMCLogServiceCollection : public Node
1155{
1156 public:
1157 template <typename CrowApp>
1158 BMCLogServiceCollection(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001159 Node(app, "/redfish/v1/Managers/bmc/LogServices/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001160 {
Ed Tanous1da66f72018-07-27 16:13:37 -07001161 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001162 {boost::beast::http::verb::get, {{"Login"}}},
1163 {boost::beast::http::verb::head, {{"Login"}}},
1164 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1165 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1166 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1167 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001168 }
1169
1170 private:
1171 /**
1172 * Functions triggers appropriate requests on DBus
1173 */
1174 void doGet(crow::Response &res, const crow::Request &req,
1175 const std::vector<std::string> &params) override
1176 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001177 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001178 // Collections don't include the static data added by SubRoute because
1179 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -07001180 asyncResp->res.jsonValue["@odata.type"] =
Ed Tanous1da66f72018-07-27 16:13:37 -07001181 "#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -07001182 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001183 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -07001184 asyncResp->res.jsonValue["@odata.id"] =
1185 "/redfish/v1/Managers/bmc/LogServices";
1186 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
1187 asyncResp->res.jsonValue["Description"] =
Ed Tanous1da66f72018-07-27 16:13:37 -07001188 "Collection of LogServices for this Manager";
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001189 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
1190 logServiceArray = nlohmann::json::array();
1191#ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
1192 logServiceArray.push_back(
Anthony Wilson08a4e4b2019-04-12 08:23:05 -05001193 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}});
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001194#endif
Jason M. Billse1f26342018-07-18 12:12:00 -07001195 asyncResp->res.jsonValue["Members@odata.count"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001196 logServiceArray.size();
Ed Tanous1da66f72018-07-27 16:13:37 -07001197 }
1198};
1199
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001200class BMCJournalLogService : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001201{
1202 public:
1203 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001204 BMCJournalLogService(CrowApp &app) :
1205 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
Jason M. Billse1f26342018-07-18 12:12:00 -07001206 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001207 entityPrivileges = {
1208 {boost::beast::http::verb::get, {{"Login"}}},
1209 {boost::beast::http::verb::head, {{"Login"}}},
1210 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1211 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1212 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1213 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1214 }
1215
1216 private:
1217 void doGet(crow::Response &res, const crow::Request &req,
1218 const std::vector<std::string> &params) override
1219 {
1220 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001221 asyncResp->res.jsonValue["@odata.type"] =
1222 "#LogService.v1_1_0.LogService";
Ed Tanous0f74e642018-11-12 15:17:05 -08001223 asyncResp->res.jsonValue["@odata.id"] =
1224 "/redfish/v1/Managers/bmc/LogServices/Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -07001225 asyncResp->res.jsonValue["@odata.context"] =
1226 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001227 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
1228 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
1229 asyncResp->res.jsonValue["Id"] = "BMC Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -07001230 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
Jason M. Billscd50aa42019-02-12 17:09:02 -08001231 asyncResp->res.jsonValue["Entries"] = {
1232 {"@odata.id",
Ed Tanous086be232019-05-23 11:47:09 -07001233 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}};
Jason M. Billse1f26342018-07-18 12:12:00 -07001234 }
1235};
1236
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001237static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID,
1238 sd_journal *journal,
1239 nlohmann::json &bmcJournalLogEntryJson)
Jason M. Billse1f26342018-07-18 12:12:00 -07001240{
1241 // Get the Log Entry contents
1242 int ret = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -07001243
Ed Tanous39e77502019-03-04 17:35:53 -08001244 std::string_view msg;
Jason M. Bills16428a12018-11-02 12:42:29 -07001245 ret = getJournalMetadata(journal, "MESSAGE", msg);
Jason M. Billse1f26342018-07-18 12:12:00 -07001246 if (ret < 0)
1247 {
1248 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
1249 return 1;
1250 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001251
1252 // Get the severity from the PRIORITY field
Ed Tanous271584a2019-07-09 16:24:22 -07001253 long int severity = 8; // Default to an invalid priority
Jason M. Bills16428a12018-11-02 12:42:29 -07001254 ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
Jason M. Billse1f26342018-07-18 12:12:00 -07001255 if (ret < 0)
1256 {
1257 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
Jason M. Billse1f26342018-07-18 12:12:00 -07001258 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001259
1260 // Get the Created time from the timestamp
Jason M. Bills16428a12018-11-02 12:42:29 -07001261 std::string entryTimeStr;
1262 if (!getEntryTimestamp(journal, entryTimeStr))
Jason M. Billse1f26342018-07-18 12:12:00 -07001263 {
Jason M. Bills16428a12018-11-02 12:42:29 -07001264 return 1;
Jason M. Billse1f26342018-07-18 12:12:00 -07001265 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001266
1267 // Fill in the log entry with the gathered data
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001268 bmcJournalLogEntryJson = {
Andrew Geisslercb92c032018-08-17 07:56:14 -07001269 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
Jason M. Billse1f26342018-07-18 12:12:00 -07001270 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001271 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
1272 bmcJournalLogEntryID},
Jason M. Billse1f26342018-07-18 12:12:00 -07001273 {"Name", "BMC Journal Entry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001274 {"Id", bmcJournalLogEntryID},
Jason M. Bills16428a12018-11-02 12:42:29 -07001275 {"Message", msg},
Jason M. Billse1f26342018-07-18 12:12:00 -07001276 {"EntryType", "Oem"},
1277 {"Severity",
Jason M. Billsb6a61a52019-08-01 14:26:15 -07001278 severity <= 2 ? "Critical" : severity <= 4 ? "Warning" : "OK"},
Ed Tanous086be232019-05-23 11:47:09 -07001279 {"OemRecordFormat", "BMC Journal Entry"},
Jason M. Billse1f26342018-07-18 12:12:00 -07001280 {"Created", std::move(entryTimeStr)}};
1281 return 0;
1282}
1283
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001284class BMCJournalLogEntryCollection : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -07001285{
1286 public:
1287 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001288 BMCJournalLogEntryCollection(CrowApp &app) :
1289 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
Jason M. Billse1f26342018-07-18 12:12:00 -07001290 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001291 entityPrivileges = {
1292 {boost::beast::http::verb::get, {{"Login"}}},
1293 {boost::beast::http::verb::head, {{"Login"}}},
1294 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1295 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1296 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1297 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1298 }
1299
1300 private:
1301 void doGet(crow::Response &res, const crow::Request &req,
1302 const std::vector<std::string> &params) override
1303 {
1304 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001305 static constexpr const long maxEntriesPerPage = 1000;
Ed Tanous271584a2019-07-09 16:24:22 -07001306 uint64_t skip = 0;
1307 uint64_t top = maxEntriesPerPage; // Show max entries by default
Jason M. Bills16428a12018-11-02 12:42:29 -07001308 if (!getSkipParam(asyncResp->res, req, skip))
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001309 {
Jason M. Bills16428a12018-11-02 12:42:29 -07001310 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001311 }
Jason M. Bills16428a12018-11-02 12:42:29 -07001312 if (!getTopParam(asyncResp->res, req, top))
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001313 {
Jason M. Bills16428a12018-11-02 12:42:29 -07001314 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001315 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001316 // Collections don't include the static data added by SubRoute because
1317 // it has a duplicate entry for members
1318 asyncResp->res.jsonValue["@odata.type"] =
1319 "#LogEntryCollection.LogEntryCollection";
Ed Tanous0f74e642018-11-12 15:17:05 -08001320 asyncResp->res.jsonValue["@odata.id"] =
1321 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001322 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001323 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -07001324 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001325 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001326 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
1327 asyncResp->res.jsonValue["Description"] =
1328 "Collection of BMC Journal Entries";
Ed Tanous0f74e642018-11-12 15:17:05 -08001329 asyncResp->res.jsonValue["@odata.id"] =
1330 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001331 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1332 logEntryArray = nlohmann::json::array();
1333
1334 // Go through the journal and use the timestamp to create a unique ID
1335 // for each entry
1336 sd_journal *journalTmp = nullptr;
1337 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1338 if (ret < 0)
1339 {
1340 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -07001341 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001342 return;
1343 }
1344 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1345 journalTmp, sd_journal_close);
1346 journalTmp = nullptr;
Ed Tanousb01bf292019-03-25 19:25:26 +00001347 uint64_t entryCount = 0;
Jason M. Billse85d6b12019-07-29 17:01:15 -07001348 // Reset the unique ID on the first entry
1349 bool firstEntry = true;
Jason M. Billse1f26342018-07-18 12:12:00 -07001350 SD_JOURNAL_FOREACH(journal.get())
1351 {
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001352 entryCount++;
1353 // Handle paging using skip (number of entries to skip from the
1354 // start) and top (number of entries to display)
1355 if (entryCount <= skip || entryCount > skip + top)
1356 {
1357 continue;
1358 }
1359
Jason M. Bills16428a12018-11-02 12:42:29 -07001360 std::string idStr;
Jason M. Billse85d6b12019-07-29 17:01:15 -07001361 if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
Jason M. Billse1f26342018-07-18 12:12:00 -07001362 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001363 continue;
1364 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001365
Jason M. Billse85d6b12019-07-29 17:01:15 -07001366 if (firstEntry)
1367 {
1368 firstEntry = false;
1369 }
1370
Jason M. Billse1f26342018-07-18 12:12:00 -07001371 logEntryArray.push_back({});
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001372 nlohmann::json &bmcJournalLogEntry = logEntryArray.back();
1373 if (fillBMCJournalLogEntryJson(idStr, journal.get(),
1374 bmcJournalLogEntry) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -07001375 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001376 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001377 return;
1378 }
1379 }
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001380 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
1381 if (skip + top < entryCount)
1382 {
1383 asyncResp->res.jsonValue["Members@odata.nextLink"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001384 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001385 std::to_string(skip + top);
1386 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001387 }
1388};
1389
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001390class BMCJournalLogEntry : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -07001391{
1392 public:
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001393 BMCJournalLogEntry(CrowApp &app) :
1394 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/",
Jason M. Billse1f26342018-07-18 12:12:00 -07001395 std::string())
1396 {
1397 entityPrivileges = {
1398 {boost::beast::http::verb::get, {{"Login"}}},
1399 {boost::beast::http::verb::head, {{"Login"}}},
1400 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1401 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1402 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1403 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1404 }
1405
1406 private:
1407 void doGet(crow::Response &res, const crow::Request &req,
1408 const std::vector<std::string> &params) override
1409 {
1410 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1411 if (params.size() != 1)
1412 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001413 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001414 return;
1415 }
Jason M. Bills16428a12018-11-02 12:42:29 -07001416 const std::string &entryID = params[0];
Jason M. Billse1f26342018-07-18 12:12:00 -07001417 // Convert the unique ID back to a timestamp to find the entry
Jason M. Billse1f26342018-07-18 12:12:00 -07001418 uint64_t ts = 0;
Ed Tanous271584a2019-07-09 16:24:22 -07001419 uint64_t index = 0;
Jason M. Bills16428a12018-11-02 12:42:29 -07001420 if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
Jason M. Billse1f26342018-07-18 12:12:00 -07001421 {
Jason M. Bills16428a12018-11-02 12:42:29 -07001422 return;
Jason M. Billse1f26342018-07-18 12:12:00 -07001423 }
1424
1425 sd_journal *journalTmp = nullptr;
1426 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1427 if (ret < 0)
1428 {
1429 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -07001430 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001431 return;
1432 }
1433 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1434 journalTmp, sd_journal_close);
1435 journalTmp = nullptr;
1436 // Go to the timestamp in the log and move to the entry at the index
Jason M. Billsaf07e3f2019-08-01 14:41:39 -07001437 // tracking the unique ID
1438 std::string idStr;
1439 bool firstEntry = true;
Jason M. Billse1f26342018-07-18 12:12:00 -07001440 ret = sd_journal_seek_realtime_usec(journal.get(), ts);
Ed Tanous271584a2019-07-09 16:24:22 -07001441 for (uint64_t i = 0; i <= index; i++)
Jason M. Billse1f26342018-07-18 12:12:00 -07001442 {
1443 sd_journal_next(journal.get());
Jason M. Billsaf07e3f2019-08-01 14:41:39 -07001444 if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
1445 {
1446 messages::internalError(asyncResp->res);
1447 return;
1448 }
1449 if (firstEntry)
1450 {
1451 firstEntry = false;
1452 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001453 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001454 // Confirm that the entry ID matches what was requested
Jason M. Billsaf07e3f2019-08-01 14:41:39 -07001455 if (idStr != entryID)
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001456 {
1457 messages::resourceMissingAtURI(asyncResp->res, entryID);
1458 return;
1459 }
1460
1461 if (fillBMCJournalLogEntryJson(entryID, journal.get(),
1462 asyncResp->res.jsonValue) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -07001463 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001464 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001465 return;
1466 }
1467 }
1468};
1469
Jason M. Bills424c4172019-03-21 13:50:33 -07001470class CrashdumpService : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -07001471{
1472 public:
1473 template <typename CrowApp>
Jason M. Bills424c4172019-03-21 13:50:33 -07001474 CrashdumpService(CrowApp &app) :
1475 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001476 {
Ed Tanous1da66f72018-07-27 16:13:37 -07001477 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001478 {boost::beast::http::verb::get, {{"Login"}}},
1479 {boost::beast::http::verb::head, {{"Login"}}},
1480 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1481 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1482 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1483 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001484 }
1485
1486 private:
1487 /**
1488 * Functions triggers appropriate requests on DBus
1489 */
1490 void doGet(crow::Response &res, const crow::Request &req,
1491 const std::vector<std::string> &params) override
1492 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001493 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001494 // Copy over the static data to include the entries added by SubRoute
Ed Tanous0f74e642018-11-12 15:17:05 -08001495 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Bills424c4172019-03-21 13:50:33 -07001496 "/redfish/v1/Systems/system/LogServices/Crashdump";
Jason M. Billse1f26342018-07-18 12:12:00 -07001497 asyncResp->res.jsonValue["@odata.type"] =
1498 "#LogService.v1_1_0.LogService";
1499 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001500 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Bills424c4172019-03-21 13:50:33 -07001501 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Service";
1502 asyncResp->res.jsonValue["Description"] = "Crashdump Service";
1503 asyncResp->res.jsonValue["Id"] = "Crashdump";
Jason M. Billse1f26342018-07-18 12:12:00 -07001504 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
1505 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
Jason M. Billscd50aa42019-02-12 17:09:02 -08001506 asyncResp->res.jsonValue["Entries"] = {
1507 {"@odata.id",
Jason M. Bills424c4172019-03-21 13:50:33 -07001508 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}};
Jason M. Billse1f26342018-07-18 12:12:00 -07001509 asyncResp->res.jsonValue["Actions"] = {
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001510 {"#LogService.ClearLog",
1511 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1512 "Actions/LogService.ClearLog"}}},
Ed Tanous1da66f72018-07-27 16:13:37 -07001513 {"Oem",
Jason M. Bills424c4172019-03-21 13:50:33 -07001514 {{"#Crashdump.OnDemand",
1515 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1516 "Actions/Oem/Crashdump.OnDemand"}}}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001517
1518#ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
Jason M. Billse1f26342018-07-18 12:12:00 -07001519 asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
Jason M. Bills424c4172019-03-21 13:50:33 -07001520 {"#Crashdump.SendRawPeci",
Anthony Wilson08a4e4b2019-04-12 08:23:05 -05001521 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1522 "Actions/Oem/Crashdump.SendRawPeci"}}});
Ed Tanous1da66f72018-07-27 16:13:37 -07001523#endif
Ed Tanous1da66f72018-07-27 16:13:37 -07001524 }
1525};
1526
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001527class CrashdumpClear : public Node
1528{
1529 public:
1530 CrashdumpClear(CrowApp &app) :
1531 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/"
1532 "LogService.ClearLog/")
1533 {
1534 entityPrivileges = {
1535 {boost::beast::http::verb::get, {{"Login"}}},
1536 {boost::beast::http::verb::head, {{"Login"}}},
1537 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1538 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1539 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1540 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1541 }
1542
1543 private:
1544 void doPost(crow::Response &res, const crow::Request &req,
1545 const std::vector<std::string> &params) override
1546 {
1547 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1548
1549 crow::connections::systemBus->async_method_call(
1550 [asyncResp](const boost::system::error_code ec,
1551 const std::string &resp) {
1552 if (ec)
1553 {
1554 messages::internalError(asyncResp->res);
1555 return;
1556 }
1557 messages::success(asyncResp->res);
1558 },
1559 crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll");
1560 }
1561};
1562
Jason M. Billse855dd22019-10-08 11:37:48 -07001563std::string getLogCreatedTime(const std::string &crashdump)
1564{
1565 nlohmann::json crashdumpJson =
1566 nlohmann::json::parse(crashdump, nullptr, false);
1567 if (crashdumpJson.is_discarded())
1568 {
1569 return std::string();
1570 }
1571
1572 nlohmann::json::const_iterator cdIt = crashdumpJson.find("crash_data");
1573 if (cdIt == crashdumpJson.end())
1574 {
1575 return std::string();
1576 }
1577
1578 nlohmann::json::const_iterator siIt = cdIt->find("METADATA");
1579 if (siIt == cdIt->end())
1580 {
1581 return std::string();
1582 }
1583
1584 nlohmann::json::const_iterator tsIt = siIt->find("timestamp");
1585 if (tsIt == siIt->end())
1586 {
1587 return std::string();
1588 }
1589
1590 const std::string *logTime = tsIt->get_ptr<const std::string *>();
1591 if (logTime == nullptr)
1592 {
1593 return std::string();
1594 }
1595
1596 std::string redfishDateTime = *logTime;
1597 if (redfishDateTime.length() > 2)
1598 {
1599 redfishDateTime.insert(redfishDateTime.end() - 2, ':');
1600 }
1601
1602 return redfishDateTime;
1603}
1604
1605std::string getLogFileName(const std::string &logTime)
1606{
1607 // Set the crashdump file name to "crashdump_<logTime>.json" using the
1608 // created time without the timezone info
1609 std::string fileTime = logTime;
1610 size_t plusPos = fileTime.rfind('+');
1611 if (plusPos != std::string::npos)
1612 {
1613 fileTime.erase(plusPos);
1614 }
1615 return "crashdump_" + fileTime + ".json";
1616}
1617
1618static void logCrashdumpEntry(std::shared_ptr<AsyncResp> asyncResp,
1619 const std::string &logID,
1620 nlohmann::json &logEntryJson)
1621{
1622 auto getStoredLogCallback = [asyncResp, logID, &logEntryJson](
1623 const boost::system::error_code ec,
1624 const std::variant<std::string> &resp) {
1625 if (ec)
1626 {
1627 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
1628 messages::internalError(asyncResp->res);
1629 return;
1630 }
1631 const std::string *log = std::get_if<std::string>(&resp);
1632 if (log == nullptr)
1633 {
1634 messages::internalError(asyncResp->res);
1635 return;
1636 }
1637 std::string logTime = getLogCreatedTime(*log);
1638 std::string fileName = getLogFileName(logTime);
1639
1640 logEntryJson = {
1641 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1642 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1643 {"@odata.id",
1644 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
1645 logID},
1646 {"Name", "CPU Crashdump"},
1647 {"Id", logID},
1648 {"EntryType", "Oem"},
1649 {"OemRecordFormat", "Crashdump URI"},
1650 {"Message",
1651 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
1652 logID + "/" + fileName},
1653 {"Created", std::move(logTime)}};
1654 };
1655 crow::connections::systemBus->async_method_call(
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001656 std::move(getStoredLogCallback), crashdumpObject,
1657 crashdumpPath + std::string("/") + logID,
1658 "org.freedesktop.DBus.Properties", "Get", crashdumpInterface, "Log");
Jason M. Billse855dd22019-10-08 11:37:48 -07001659}
1660
Jason M. Bills424c4172019-03-21 13:50:33 -07001661class CrashdumpEntryCollection : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001662{
1663 public:
1664 template <typename CrowApp>
Jason M. Bills424c4172019-03-21 13:50:33 -07001665 CrashdumpEntryCollection(CrowApp &app) :
1666 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001667 {
Ed Tanous1da66f72018-07-27 16:13:37 -07001668 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001669 {boost::beast::http::verb::get, {{"Login"}}},
1670 {boost::beast::http::verb::head, {{"Login"}}},
1671 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1672 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1673 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1674 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001675 }
1676
1677 private:
1678 /**
1679 * Functions triggers appropriate requests on DBus
1680 */
1681 void doGet(crow::Response &res, const crow::Request &req,
1682 const std::vector<std::string> &params) override
1683 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001684 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001685 // Collections don't include the static data added by SubRoute because
1686 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -07001687 auto getLogEntriesCallback = [asyncResp](
1688 const boost::system::error_code ec,
1689 const std::vector<std::string> &resp) {
1690 if (ec)
1691 {
1692 if (ec.value() !=
1693 boost::system::errc::no_such_file_or_directory)
Ed Tanous1da66f72018-07-27 16:13:37 -07001694 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001695 BMCWEB_LOG_DEBUG << "failed to get entries ec: "
1696 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001697 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001698 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07001699 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001700 }
1701 asyncResp->res.jsonValue["@odata.type"] =
1702 "#LogEntryCollection.LogEntryCollection";
Ed Tanous0f74e642018-11-12 15:17:05 -08001703 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Bills424c4172019-03-21 13:50:33 -07001704 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001705 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billse855dd22019-10-08 11:37:48 -07001706 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Bills424c4172019-03-21 13:50:33 -07001707 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001708 asyncResp->res.jsonValue["Description"] =
Jason M. Bills424c4172019-03-21 13:50:33 -07001709 "Collection of Crashdump Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001710 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1711 logEntryArray = nlohmann::json::array();
Jason M. Billse855dd22019-10-08 11:37:48 -07001712 std::vector<std::string> logIDs;
1713 // Get the list of log entries and build up an empty array big
1714 // enough to hold them
Jason M. Billse1f26342018-07-18 12:12:00 -07001715 for (const std::string &objpath : resp)
1716 {
Jason M. Billse855dd22019-10-08 11:37:48 -07001717 // Ignore the on-demand log
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001718 if (objpath.compare(crashdumpOnDemandPath) == 0)
Ed Tanous1da66f72018-07-27 16:13:37 -07001719 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001720 continue;
Ed Tanous1da66f72018-07-27 16:13:37 -07001721 }
Jason M. Billse855dd22019-10-08 11:37:48 -07001722
1723 // Get the log ID
Jason M. Billse1f26342018-07-18 12:12:00 -07001724 std::size_t lastPos = objpath.rfind("/");
Jason M. Billse855dd22019-10-08 11:37:48 -07001725 if (lastPos == std::string::npos)
Jason M. Billse1f26342018-07-18 12:12:00 -07001726 {
Jason M. Billse855dd22019-10-08 11:37:48 -07001727 continue;
Jason M. Billse1f26342018-07-18 12:12:00 -07001728 }
Jason M. Billse855dd22019-10-08 11:37:48 -07001729 logIDs.emplace_back(objpath.substr(lastPos + 1));
1730
1731 // Add a space for the log entry to the array
1732 logEntryArray.push_back({});
1733 }
1734 // Now go through and set up async calls to fill in the entries
1735 size_t index = 0;
1736 for (const std::string &logID : logIDs)
1737 {
1738 // Add the log entry to the array
1739 logCrashdumpEntry(asyncResp, logID, logEntryArray[index++]);
Jason M. Billse1f26342018-07-18 12:12:00 -07001740 }
1741 asyncResp->res.jsonValue["Members@odata.count"] =
1742 logEntryArray.size();
1743 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001744 crow::connections::systemBus->async_method_call(
1745 std::move(getLogEntriesCallback),
1746 "xyz.openbmc_project.ObjectMapper",
1747 "/xyz/openbmc_project/object_mapper",
1748 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001749 std::array<const char *, 1>{crashdumpInterface});
Ed Tanous1da66f72018-07-27 16:13:37 -07001750 }
1751};
1752
Jason M. Bills424c4172019-03-21 13:50:33 -07001753class CrashdumpEntry : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001754{
1755 public:
Jason M. Bills424c4172019-03-21 13:50:33 -07001756 CrashdumpEntry(CrowApp &app) :
Jason M. Billsd53dd412019-02-12 17:16:22 -08001757 Node(app,
Jason M. Bills424c4172019-03-21 13:50:33 -07001758 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/",
Ed Tanous1da66f72018-07-27 16:13:37 -07001759 std::string())
1760 {
1761 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001762 {boost::beast::http::verb::get, {{"Login"}}},
1763 {boost::beast::http::verb::head, {{"Login"}}},
1764 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1765 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1766 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1767 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001768 }
1769
1770 private:
1771 void doGet(crow::Response &res, const crow::Request &req,
1772 const std::vector<std::string> &params) override
1773 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001774 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001775 if (params.size() != 1)
1776 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001777 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001778 return;
1779 }
Jason M. Billse855dd22019-10-08 11:37:48 -07001780 const std::string &logID = params[0];
1781 logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue);
1782 }
1783};
1784
1785class CrashdumpFile : public Node
1786{
1787 public:
1788 CrashdumpFile(CrowApp &app) :
1789 Node(app,
1790 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/"
1791 "<str>/",
1792 std::string(), std::string())
1793 {
1794 entityPrivileges = {
1795 {boost::beast::http::verb::get, {{"Login"}}},
1796 {boost::beast::http::verb::head, {{"Login"}}},
1797 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1798 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1799 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1800 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1801 }
1802
1803 private:
1804 void doGet(crow::Response &res, const crow::Request &req,
1805 const std::vector<std::string> &params) override
1806 {
1807 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1808 if (params.size() != 2)
1809 {
1810 messages::internalError(asyncResp->res);
1811 return;
1812 }
1813 const std::string &logID = params[0];
1814 const std::string &fileName = params[1];
1815
1816 auto getStoredLogCallback = [asyncResp, logID, fileName](
Ed Tanousabf2add2019-01-22 16:40:12 -08001817 const boost::system::error_code ec,
1818 const std::variant<std::string> &resp) {
1819 if (ec)
1820 {
1821 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
1822 messages::internalError(asyncResp->res);
1823 return;
1824 }
1825 const std::string *log = std::get_if<std::string>(&resp);
1826 if (log == nullptr)
1827 {
1828 messages::internalError(asyncResp->res);
1829 return;
1830 }
Jason M. Billse855dd22019-10-08 11:37:48 -07001831
1832 // Verify the file name parameter is correct
1833 if (fileName != getLogFileName(getLogCreatedTime(*log)))
Ed Tanousabf2add2019-01-22 16:40:12 -08001834 {
Jason M. Billse855dd22019-10-08 11:37:48 -07001835 messages::resourceMissingAtURI(asyncResp->res, fileName);
Ed Tanousabf2add2019-01-22 16:40:12 -08001836 return;
1837 }
Jason M. Billse855dd22019-10-08 11:37:48 -07001838
1839 // Configure this to be a file download when accessed from a browser
1840 asyncResp->res.addHeader("Content-Disposition", "attachment");
1841 asyncResp->res.body() = *log;
Ed Tanousabf2add2019-01-22 16:40:12 -08001842 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001843 crow::connections::systemBus->async_method_call(
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001844 std::move(getStoredLogCallback), crashdumpObject,
1845 crashdumpPath + std::string("/") + logID,
1846 "org.freedesktop.DBus.Properties", "Get", crashdumpInterface,
Jason M. Bills424c4172019-03-21 13:50:33 -07001847 "Log");
Ed Tanous1da66f72018-07-27 16:13:37 -07001848 }
1849};
1850
Jason M. Bills424c4172019-03-21 13:50:33 -07001851class OnDemandCrashdump : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001852{
1853 public:
Jason M. Bills424c4172019-03-21 13:50:33 -07001854 OnDemandCrashdump(CrowApp &app) :
1855 Node(app,
1856 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
1857 "Crashdump.OnDemand/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001858 {
1859 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001860 {boost::beast::http::verb::get, {{"Login"}}},
1861 {boost::beast::http::verb::head, {{"Login"}}},
1862 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1863 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1864 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1865 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001866 }
1867
1868 private:
1869 void doPost(crow::Response &res, const crow::Request &req,
1870 const std::vector<std::string> &params) override
1871 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001872 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Bills48e46392019-02-13 12:58:37 -08001873 static std::unique_ptr<sdbusplus::bus::match::match> onDemandLogMatcher;
Ed Tanous1da66f72018-07-27 16:13:37 -07001874
Jason M. Bills48e46392019-02-13 12:58:37 -08001875 // Only allow one OnDemand Log request at a time
1876 if (onDemandLogMatcher != nullptr)
Ed Tanous1da66f72018-07-27 16:13:37 -07001877 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001878 messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
Ed Tanous1da66f72018-07-27 16:13:37 -07001879 return;
1880 }
1881 // Make this static so it survives outside this method
Ed Tanous271584a2019-07-09 16:24:22 -07001882 static boost::asio::steady_timer timeout(*req.ioService);
Ed Tanous1da66f72018-07-27 16:13:37 -07001883
Ed Tanous271584a2019-07-09 16:24:22 -07001884 timeout.expires_after(std::chrono::seconds(30));
Jason M. Billse1f26342018-07-18 12:12:00 -07001885 timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
Jason M. Bills48e46392019-02-13 12:58:37 -08001886 onDemandLogMatcher = nullptr;
Ed Tanous1da66f72018-07-27 16:13:37 -07001887 if (ec)
1888 {
1889 // operation_aborted is expected if timer is canceled before
1890 // completion.
1891 if (ec != boost::asio::error::operation_aborted)
1892 {
1893 BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
1894 }
1895 return;
1896 }
Jason M. Bills48e46392019-02-13 12:58:37 -08001897 BMCWEB_LOG_ERROR << "Timed out waiting for on-demand log";
Ed Tanous1da66f72018-07-27 16:13:37 -07001898
Jason M. Billsf12894f2018-10-09 12:45:45 -07001899 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001900 });
1901
Jason M. Bills3cf8ea32019-10-08 11:43:32 -07001902 auto onDemandLogMatcherCallback =
1903 [asyncResp](sdbusplus::message::message &m) {
1904 BMCWEB_LOG_DEBUG << "OnDemand log available match fired";
1905 timeout.cancel();
Ed Tanous271584a2019-07-09 16:24:22 -07001906
Jason M. Bills3cf8ea32019-10-08 11:43:32 -07001907 sdbusplus::message::object_path objPath;
1908 boost::container::flat_map<
1909 std::string, boost::container::flat_map<
1910 std::string, std::variant<std::string>>>
1911 interfacesAdded;
1912 m.read(objPath, interfacesAdded);
1913 const std::string *log = std::get_if<std::string>(
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001914 &interfacesAdded[crashdumpInterface]["Log"]);
Jason M. Bills3cf8ea32019-10-08 11:43:32 -07001915 if (log == nullptr)
1916 {
1917 messages::internalError(asyncResp->res);
1918 // Careful with onDemandLogMatcher. It is a unique_ptr to
1919 // the match object inside which this lambda is executing.
1920 // Once it is set to nullptr, the match object will be
1921 // destroyed and the lambda will lose its context, including
1922 // res, so it needs to be the last thing done.
1923 onDemandLogMatcher = nullptr;
1924 return;
1925 }
1926 nlohmann::json crashdumpJson =
1927 nlohmann::json::parse(*log, nullptr, false);
1928 if (crashdumpJson.is_discarded())
1929 {
1930 messages::internalError(asyncResp->res);
1931 // Careful with onDemandLogMatcher. It is a unique_ptr to
1932 // the match object inside which this lambda is executing.
1933 // Once it is set to nullptr, the match object will be
1934 // destroyed and the lambda will lose its context, including
1935 // res, so it needs to be the last thing done.
1936 onDemandLogMatcher = nullptr;
1937 return;
1938 }
1939 asyncResp->res.jsonValue = crashdumpJson;
Jason M. Bills48e46392019-02-13 12:58:37 -08001940 // Careful with onDemandLogMatcher. It is a unique_ptr to the
Ed Tanous1da66f72018-07-27 16:13:37 -07001941 // match object inside which this lambda is executing. Once it
1942 // is set to nullptr, the match object will be destroyed and the
1943 // lambda will lose its context, including res, so it needs to
1944 // be the last thing done.
Jason M. Bills48e46392019-02-13 12:58:37 -08001945 onDemandLogMatcher = nullptr;
Jason M. Bills3cf8ea32019-10-08 11:43:32 -07001946 };
Jason M. Bills48e46392019-02-13 12:58:37 -08001947 onDemandLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
Ed Tanous1da66f72018-07-27 16:13:37 -07001948 *crow::connections::systemBus,
1949 sdbusplus::bus::match::rules::interfacesAdded() +
Jason M. Bills424c4172019-03-21 13:50:33 -07001950 sdbusplus::bus::match::rules::argNpath(0,
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001951 crashdumpOnDemandPath),
Jason M. Bills48e46392019-02-13 12:58:37 -08001952 std::move(onDemandLogMatcherCallback));
Ed Tanous1da66f72018-07-27 16:13:37 -07001953
Jason M. Bills48e46392019-02-13 12:58:37 -08001954 auto generateonDemandLogCallback =
Jason M. Billse1f26342018-07-18 12:12:00 -07001955 [asyncResp](const boost::system::error_code ec,
1956 const std::string &resp) {
Ed Tanous1da66f72018-07-27 16:13:37 -07001957 if (ec)
1958 {
1959 if (ec.value() ==
1960 boost::system::errc::operation_not_supported)
1961 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001962 messages::resourceInStandby(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001963 }
1964 else
1965 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001966 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001967 }
Ed Tanous271584a2019-07-09 16:24:22 -07001968
1969 timeout.cancel();
Jason M. Bills48e46392019-02-13 12:58:37 -08001970 onDemandLogMatcher = nullptr;
Ed Tanous1da66f72018-07-27 16:13:37 -07001971 return;
1972 }
1973 };
1974 crow::connections::systemBus->async_method_call(
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001975 std::move(generateonDemandLogCallback), crashdumpObject,
1976 crashdumpPath, crashdumpOnDemandInterface, "GenerateOnDemandLog");
Ed Tanous1da66f72018-07-27 16:13:37 -07001977 }
1978};
1979
Jason M. Billse1f26342018-07-18 12:12:00 -07001980class SendRawPECI : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001981{
1982 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001983 SendRawPECI(CrowApp &app) :
Jason M. Bills424c4172019-03-21 13:50:33 -07001984 Node(app,
1985 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
1986 "Crashdump.SendRawPeci/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001987 {
1988 entityPrivileges = {
1989 {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
1990 {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
1991 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1992 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1993 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1994 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1995 }
1996
1997 private:
1998 void doPost(crow::Response &res, const crow::Request &req,
1999 const std::vector<std::string> &params) override
2000 {
Jason M. Billse1f26342018-07-18 12:12:00 -07002001 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanousb1556422018-10-16 14:09:17 -07002002 uint8_t clientAddress = 0;
2003 uint8_t readLength = 0;
Ed Tanous1da66f72018-07-27 16:13:37 -07002004 std::vector<uint8_t> peciCommand;
Ed Tanousb1556422018-10-16 14:09:17 -07002005 if (!json_util::readJson(req, res, "ClientAddress", clientAddress,
2006 "ReadLength", readLength, "PECICommand",
2007 peciCommand))
Ed Tanous1da66f72018-07-27 16:13:37 -07002008 {
Ed Tanousb1556422018-10-16 14:09:17 -07002009 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07002010 }
Ed Tanousb1556422018-10-16 14:09:17 -07002011
Ed Tanous1da66f72018-07-27 16:13:37 -07002012 // Callback to return the Raw PECI response
Jason M. Billse1f26342018-07-18 12:12:00 -07002013 auto sendRawPECICallback =
2014 [asyncResp](const boost::system::error_code ec,
2015 const std::vector<uint8_t> &resp) {
2016 if (ec)
2017 {
2018 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
2019 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07002020 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07002021 return;
2022 }
2023 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
2024 {"PECIResponse", resp}};
2025 };
Ed Tanous1da66f72018-07-27 16:13:37 -07002026 // Call the SendRawPECI command with the provided data
2027 crow::connections::systemBus->async_method_call(
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07002028 std::move(sendRawPECICallback), crashdumpObject, crashdumpPath,
2029 crashdumpRawPECIInterface, "SendRawPeci", clientAddress, readLength,
Ed Tanous4ed77cd2018-10-15 08:08:07 -07002030 peciCommand);
Ed Tanous1da66f72018-07-27 16:13:37 -07002031 }
2032};
2033
Andrew Geisslercb92c032018-08-17 07:56:14 -07002034/**
2035 * DBusLogServiceActionsClear class supports POST method for ClearLog action.
2036 */
2037class DBusLogServiceActionsClear : public Node
2038{
2039 public:
2040 DBusLogServiceActionsClear(CrowApp &app) :
2041 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
Tim Lee1f56a3a2019-10-09 10:17:57 +08002042 "LogService.ClearLog")
Andrew Geisslercb92c032018-08-17 07:56:14 -07002043 {
2044 entityPrivileges = {
2045 {boost::beast::http::verb::get, {{"Login"}}},
2046 {boost::beast::http::verb::head, {{"Login"}}},
2047 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2048 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2049 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2050 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2051 }
2052
2053 private:
2054 /**
2055 * Function handles POST method request.
2056 * The Clear Log actions does not require any parameter.The action deletes
2057 * all entries found in the Entries collection for this Log Service.
2058 */
2059 void doPost(crow::Response &res, const crow::Request &req,
2060 const std::vector<std::string> &params) override
2061 {
2062 BMCWEB_LOG_DEBUG << "Do delete all entries.";
2063
2064 auto asyncResp = std::make_shared<AsyncResp>(res);
2065 // Process response from Logging service.
2066 auto resp_handler = [asyncResp](const boost::system::error_code ec) {
2067 BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done";
2068 if (ec)
2069 {
2070 // TODO Handle for specific error code
2071 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec;
2072 asyncResp->res.result(
2073 boost::beast::http::status::internal_server_error);
2074 return;
2075 }
2076
2077 asyncResp->res.result(boost::beast::http::status::no_content);
2078 };
2079
2080 // Make call to Logging service to request Clear Log
2081 crow::connections::systemBus->async_method_call(
2082 resp_handler, "xyz.openbmc_project.Logging",
2083 "/xyz/openbmc_project/logging",
2084 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
2085 }
2086};
Ed Tanous1da66f72018-07-27 16:13:37 -07002087} // namespace redfish