blob: 1447f21549e778c9f197d0259c4a4ee93a9222af [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>
Jason M. Bills1ddcf012019-11-26 14:59:21 -080028#include <boost/system/linux_error.hpp>
Andrew Geisslercb92c032018-08-17 07:56:14 -070029#include <error_messages.hpp>
James Feist4418c7f2019-04-15 11:09:15 -070030#include <filesystem>
Jason M. Billscd225da2019-05-08 15:31:57 -070031#include <string_view>
Ed Tanousabf2add2019-01-22 16:40:12 -080032#include <variant>
Ed Tanous1da66f72018-07-27 16:13:37 -070033
34namespace redfish
35{
36
Jason M. Bills5b61b5e2019-10-16 10:59:02 -070037constexpr char const *crashdumpObject = "com.intel.crashdump";
38constexpr char const *crashdumpPath = "/com/intel/crashdump";
39constexpr char const *crashdumpOnDemandPath = "/com/intel/crashdump/OnDemand";
40constexpr char const *crashdumpInterface = "com.intel.crashdump";
41constexpr char const *deleteAllInterface =
42 "xyz.openbmc_project.Collection.DeleteAll";
43constexpr char const *crashdumpOnDemandInterface =
Jason M. Bills424c4172019-03-21 13:50:33 -070044 "com.intel.crashdump.OnDemand";
Jason M. Bills5b61b5e2019-10-16 10:59:02 -070045constexpr char const *crashdumpRawPECIInterface =
Jason M. Bills424c4172019-03-21 13:50:33 -070046 "com.intel.crashdump.SendRawPeci";
Ed Tanous1da66f72018-07-27 16:13:37 -070047
Jason M. Bills4851d452019-03-28 11:27:48 -070048namespace message_registries
49{
50static const Message *getMessageFromRegistry(
51 const std::string &messageKey,
52 const boost::beast::span<const MessageEntry> registry)
53{
54 boost::beast::span<const MessageEntry>::const_iterator messageIt =
55 std::find_if(registry.cbegin(), registry.cend(),
56 [&messageKey](const MessageEntry &messageEntry) {
57 return !std::strcmp(messageEntry.first,
58 messageKey.c_str());
59 });
60 if (messageIt != registry.cend())
61 {
62 return &messageIt->second;
63 }
64
65 return nullptr;
66}
67
68static const Message *getMessage(const std::string_view &messageID)
69{
70 // Redfish MessageIds are in the form
71 // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find
72 // the right Message
73 std::vector<std::string> fields;
74 fields.reserve(4);
75 boost::split(fields, messageID, boost::is_any_of("."));
76 std::string &registryName = fields[0];
77 std::string &messageKey = fields[3];
78
79 // Find the right registry and check it for the MessageKey
80 if (std::string(base::header.registryPrefix) == registryName)
81 {
82 return getMessageFromRegistry(
83 messageKey, boost::beast::span<const MessageEntry>(base::registry));
84 }
85 if (std::string(openbmc::header.registryPrefix) == registryName)
86 {
87 return getMessageFromRegistry(
88 messageKey,
89 boost::beast::span<const MessageEntry>(openbmc::registry));
90 }
91 return nullptr;
92}
93} // namespace message_registries
94
James Feistf6150402019-01-08 10:36:20 -080095namespace fs = std::filesystem;
Ed Tanous1da66f72018-07-27 16:13:37 -070096
Andrew Geisslercb92c032018-08-17 07:56:14 -070097using GetManagedPropertyType = boost::container::flat_map<
98 std::string,
99 sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t,
100 int32_t, uint32_t, int64_t, uint64_t, double>>;
101
102using GetManagedObjectsType = boost::container::flat_map<
103 sdbusplus::message::object_path,
104 boost::container::flat_map<std::string, GetManagedPropertyType>>;
105
106inline std::string translateSeverityDbusToRedfish(const std::string &s)
107{
108 if (s == "xyz.openbmc_project.Logging.Entry.Level.Alert")
109 {
110 return "Critical";
111 }
112 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Critical")
113 {
114 return "Critical";
115 }
116 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Debug")
117 {
118 return "OK";
119 }
120 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency")
121 {
122 return "Critical";
123 }
124 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Error")
125 {
126 return "Critical";
127 }
128 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Informational")
129 {
130 return "OK";
131 }
132 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Notice")
133 {
134 return "OK";
135 }
136 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
137 {
138 return "Warning";
139 }
140 return "";
141}
142
Jason M. Bills16428a12018-11-02 12:42:29 -0700143static int getJournalMetadata(sd_journal *journal,
Ed Tanous39e77502019-03-04 17:35:53 -0800144 const std::string_view &field,
145 std::string_view &contents)
Jason M. Bills16428a12018-11-02 12:42:29 -0700146{
147 const char *data = nullptr;
148 size_t length = 0;
149 int ret = 0;
150 // Get the metadata from the requested field of the journal entry
Ed Tanous271584a2019-07-09 16:24:22 -0700151 ret = sd_journal_get_data(journal, field.data(),
152 reinterpret_cast<const void **>(&data), &length);
Jason M. Bills16428a12018-11-02 12:42:29 -0700153 if (ret < 0)
154 {
155 return ret;
156 }
Ed Tanous39e77502019-03-04 17:35:53 -0800157 contents = std::string_view(data, length);
Jason M. Bills16428a12018-11-02 12:42:29 -0700158 // Only use the content after the "=" character.
159 contents.remove_prefix(std::min(contents.find("=") + 1, contents.size()));
160 return ret;
161}
162
163static int getJournalMetadata(sd_journal *journal,
Ed Tanous39e77502019-03-04 17:35:53 -0800164 const std::string_view &field, const int &base,
Ed Tanous271584a2019-07-09 16:24:22 -0700165 long int &contents)
Jason M. Bills16428a12018-11-02 12:42:29 -0700166{
167 int ret = 0;
Ed Tanous39e77502019-03-04 17:35:53 -0800168 std::string_view metadata;
Jason M. Bills16428a12018-11-02 12:42:29 -0700169 // Get the metadata from the requested field of the journal entry
170 ret = getJournalMetadata(journal, field, metadata);
171 if (ret < 0)
172 {
173 return ret;
174 }
Ed Tanousb01bf292019-03-25 19:25:26 +0000175 contents = strtol(metadata.data(), nullptr, base);
Jason M. Bills16428a12018-11-02 12:42:29 -0700176 return ret;
177}
178
179static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp)
180{
181 int ret = 0;
182 uint64_t timestamp = 0;
183 ret = sd_journal_get_realtime_usec(journal, &timestamp);
184 if (ret < 0)
185 {
186 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
187 << strerror(-ret);
188 return false;
189 }
190 time_t t =
191 static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s
192 struct tm *loctime = localtime(&t);
193 char entryTime[64] = {};
Ed Tanous99131cd2019-10-24 11:12:47 -0700194 if (nullptr != loctime)
Jason M. Bills16428a12018-11-02 12:42:29 -0700195 {
196 strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime);
197 }
198 // Insert the ':' into the timezone
Ed Tanous39e77502019-03-04 17:35:53 -0800199 std::string_view t1(entryTime);
200 std::string_view t2(entryTime);
Jason M. Bills16428a12018-11-02 12:42:29 -0700201 if (t1.size() > 2 && t2.size() > 2)
202 {
203 t1.remove_suffix(2);
204 t2.remove_prefix(t2.size() - 2);
205 }
Ed Tanous39e77502019-03-04 17:35:53 -0800206 entryTimestamp = std::string(t1) + ":" + std::string(t2);
Jason M. Bills16428a12018-11-02 12:42:29 -0700207 return true;
208}
209
210static bool getSkipParam(crow::Response &res, const crow::Request &req,
Ed Tanous271584a2019-07-09 16:24:22 -0700211 uint64_t &skip)
Jason M. Bills16428a12018-11-02 12:42:29 -0700212{
213 char *skipParam = req.urlParams.get("$skip");
214 if (skipParam != nullptr)
215 {
216 char *ptr = nullptr;
Ed Tanous271584a2019-07-09 16:24:22 -0700217 skip = std::strtoul(skipParam, &ptr, 10);
Jason M. Bills16428a12018-11-02 12:42:29 -0700218 if (*skipParam == '\0' || *ptr != '\0')
219 {
220
221 messages::queryParameterValueTypeError(res, std::string(skipParam),
222 "$skip");
223 return false;
224 }
Jason M. Bills16428a12018-11-02 12:42:29 -0700225 }
226 return true;
227}
228
Ed Tanous271584a2019-07-09 16:24:22 -0700229static constexpr const uint64_t maxEntriesPerPage = 1000;
Jason M. Bills16428a12018-11-02 12:42:29 -0700230static bool getTopParam(crow::Response &res, const crow::Request &req,
Ed Tanous271584a2019-07-09 16:24:22 -0700231 uint64_t &top)
Jason M. Bills16428a12018-11-02 12:42:29 -0700232{
233 char *topParam = req.urlParams.get("$top");
234 if (topParam != nullptr)
235 {
236 char *ptr = nullptr;
Ed Tanous271584a2019-07-09 16:24:22 -0700237 top = std::strtoul(topParam, &ptr, 10);
Jason M. Bills16428a12018-11-02 12:42:29 -0700238 if (*topParam == '\0' || *ptr != '\0')
239 {
240 messages::queryParameterValueTypeError(res, std::string(topParam),
241 "$top");
242 return false;
243 }
Ed Tanous271584a2019-07-09 16:24:22 -0700244 if (top < 1U || top > maxEntriesPerPage)
Jason M. Bills16428a12018-11-02 12:42:29 -0700245 {
246
247 messages::queryParameterOutOfRange(
248 res, std::to_string(top), "$top",
249 "1-" + std::to_string(maxEntriesPerPage));
250 return false;
251 }
252 }
253 return true;
254}
255
Jason M. Billse85d6b12019-07-29 17:01:15 -0700256static bool getUniqueEntryID(sd_journal *journal, std::string &entryID,
257 const bool firstEntry = true)
Jason M. Bills16428a12018-11-02 12:42:29 -0700258{
259 int ret = 0;
260 static uint64_t prevTs = 0;
261 static int index = 0;
Jason M. Billse85d6b12019-07-29 17:01:15 -0700262 if (firstEntry)
263 {
264 prevTs = 0;
265 }
266
Jason M. Bills16428a12018-11-02 12:42:29 -0700267 // Get the entry timestamp
268 uint64_t curTs = 0;
269 ret = sd_journal_get_realtime_usec(journal, &curTs);
270 if (ret < 0)
271 {
272 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
273 << strerror(-ret);
274 return false;
275 }
276 // If the timestamp isn't unique, increment the index
277 if (curTs == prevTs)
278 {
279 index++;
280 }
281 else
282 {
283 // Otherwise, reset it
284 index = 0;
285 }
286 // Save the timestamp
287 prevTs = curTs;
288
289 entryID = std::to_string(curTs);
290 if (index > 0)
291 {
292 entryID += "_" + std::to_string(index);
293 }
294 return true;
295}
296
Jason M. Billse85d6b12019-07-29 17:01:15 -0700297static bool getUniqueEntryID(const std::string &logEntry, std::string &entryID,
298 const bool firstEntry = true)
Jason M. Bills95820182019-04-22 16:25:34 -0700299{
Ed Tanous271584a2019-07-09 16:24:22 -0700300 static time_t prevTs = 0;
Jason M. Bills95820182019-04-22 16:25:34 -0700301 static int index = 0;
Jason M. Billse85d6b12019-07-29 17:01:15 -0700302 if (firstEntry)
303 {
304 prevTs = 0;
305 }
306
Jason M. Bills95820182019-04-22 16:25:34 -0700307 // Get the entry timestamp
Ed Tanous271584a2019-07-09 16:24:22 -0700308 std::time_t curTs = 0;
Jason M. Bills95820182019-04-22 16:25:34 -0700309 std::tm timeStruct = {};
310 std::istringstream entryStream(logEntry);
311 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
312 {
313 curTs = std::mktime(&timeStruct);
314 }
315 // If the timestamp isn't unique, increment the index
316 if (curTs == prevTs)
317 {
318 index++;
319 }
320 else
321 {
322 // Otherwise, reset it
323 index = 0;
324 }
325 // Save the timestamp
326 prevTs = curTs;
327
328 entryID = std::to_string(curTs);
329 if (index > 0)
330 {
331 entryID += "_" + std::to_string(index);
332 }
333 return true;
334}
335
Jason M. Bills16428a12018-11-02 12:42:29 -0700336static bool getTimestampFromID(crow::Response &res, const std::string &entryID,
Ed Tanous271584a2019-07-09 16:24:22 -0700337 uint64_t &timestamp, uint64_t &index)
Jason M. Bills16428a12018-11-02 12:42:29 -0700338{
339 if (entryID.empty())
340 {
341 return false;
342 }
343 // Convert the unique ID back to a timestamp to find the entry
Ed Tanous39e77502019-03-04 17:35:53 -0800344 std::string_view tsStr(entryID);
Jason M. Bills16428a12018-11-02 12:42:29 -0700345
346 auto underscorePos = tsStr.find("_");
347 if (underscorePos != tsStr.npos)
348 {
349 // Timestamp has an index
350 tsStr.remove_suffix(tsStr.size() - underscorePos);
Ed Tanous39e77502019-03-04 17:35:53 -0800351 std::string_view indexStr(entryID);
Jason M. Bills16428a12018-11-02 12:42:29 -0700352 indexStr.remove_prefix(underscorePos + 1);
353 std::size_t pos;
354 try
355 {
Ed Tanous39e77502019-03-04 17:35:53 -0800356 index = std::stoul(std::string(indexStr), &pos);
Jason M. Bills16428a12018-11-02 12:42:29 -0700357 }
Ed Tanous271584a2019-07-09 16:24:22 -0700358 catch (std::invalid_argument &)
Jason M. Bills16428a12018-11-02 12:42:29 -0700359 {
360 messages::resourceMissingAtURI(res, entryID);
361 return false;
362 }
Ed Tanous271584a2019-07-09 16:24:22 -0700363 catch (std::out_of_range &)
Jason M. Bills16428a12018-11-02 12:42:29 -0700364 {
365 messages::resourceMissingAtURI(res, entryID);
366 return false;
367 }
368 if (pos != indexStr.size())
369 {
370 messages::resourceMissingAtURI(res, entryID);
371 return false;
372 }
373 }
374 // Timestamp has no index
375 std::size_t pos;
376 try
377 {
Ed Tanous39e77502019-03-04 17:35:53 -0800378 timestamp = std::stoull(std::string(tsStr), &pos);
Jason M. Bills16428a12018-11-02 12:42:29 -0700379 }
Ed Tanous271584a2019-07-09 16:24:22 -0700380 catch (std::invalid_argument &)
Jason M. Bills16428a12018-11-02 12:42:29 -0700381 {
382 messages::resourceMissingAtURI(res, entryID);
383 return false;
384 }
Ed Tanous271584a2019-07-09 16:24:22 -0700385 catch (std::out_of_range &)
Jason M. Bills16428a12018-11-02 12:42:29 -0700386 {
387 messages::resourceMissingAtURI(res, entryID);
388 return false;
389 }
390 if (pos != tsStr.size())
391 {
392 messages::resourceMissingAtURI(res, entryID);
393 return false;
394 }
395 return true;
396}
397
Jason M. Bills95820182019-04-22 16:25:34 -0700398static bool
399 getRedfishLogFiles(std::vector<std::filesystem::path> &redfishLogFiles)
400{
401 static const std::filesystem::path redfishLogDir = "/var/log";
402 static const std::string redfishLogFilename = "redfish";
403
404 // Loop through the directory looking for redfish log files
405 for (const std::filesystem::directory_entry &dirEnt :
406 std::filesystem::directory_iterator(redfishLogDir))
407 {
408 // If we find a redfish log file, save the path
409 std::string filename = dirEnt.path().filename();
410 if (boost::starts_with(filename, redfishLogFilename))
411 {
412 redfishLogFiles.emplace_back(redfishLogDir / filename);
413 }
414 }
415 // As the log files rotate, they are appended with a ".#" that is higher for
416 // the older logs. Since we don't expect more than 10 log files, we
417 // can just sort the list to get them in order from newest to oldest
418 std::sort(redfishLogFiles.begin(), redfishLogFiles.end());
419
420 return !redfishLogFiles.empty();
421}
422
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800423class SystemLogServiceCollection : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700424{
425 public:
426 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800427 SystemLogServiceCollection(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800428 Node(app, "/redfish/v1/Systems/system/LogServices/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800429 {
430 entityPrivileges = {
431 {boost::beast::http::verb::get, {{"Login"}}},
432 {boost::beast::http::verb::head, {{"Login"}}},
433 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
434 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
435 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
436 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
437 }
438
439 private:
440 /**
441 * Functions triggers appropriate requests on DBus
442 */
443 void doGet(crow::Response &res, const crow::Request &req,
444 const std::vector<std::string> &params) override
445 {
446 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800447 // Collections don't include the static data added by SubRoute because
448 // it has a duplicate entry for members
449 asyncResp->res.jsonValue["@odata.type"] =
450 "#LogServiceCollection.LogServiceCollection";
451 asyncResp->res.jsonValue["@odata.context"] =
452 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
453 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800454 "/redfish/v1/Systems/system/LogServices";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800455 asyncResp->res.jsonValue["Name"] = "System Log Services Collection";
456 asyncResp->res.jsonValue["Description"] =
457 "Collection of LogServices for this Computer System";
458 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
459 logServiceArray = nlohmann::json::array();
Ed Tanous029573d2019-02-01 10:57:49 -0800460 logServiceArray.push_back(
461 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}});
Jason M. Billsd53dd412019-02-12 17:16:22 -0800462#ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
463 logServiceArray.push_back(
Anthony Wilson08a4e4b2019-04-12 08:23:05 -0500464 {{"@odata.id",
465 "/redfish/v1/Systems/system/LogServices/Crashdump"}});
Jason M. Billsd53dd412019-02-12 17:16:22 -0800466#endif
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800467 asyncResp->res.jsonValue["Members@odata.count"] =
468 logServiceArray.size();
469 }
470};
471
472class EventLogService : public Node
473{
474 public:
475 template <typename CrowApp>
476 EventLogService(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800477 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800478 {
479 entityPrivileges = {
480 {boost::beast::http::verb::get, {{"Login"}}},
481 {boost::beast::http::verb::head, {{"Login"}}},
482 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
483 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
484 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
485 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
486 }
487
488 private:
489 void doGet(crow::Response &res, const crow::Request &req,
490 const std::vector<std::string> &params) override
491 {
492 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
493
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800494 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800495 "/redfish/v1/Systems/system/LogServices/EventLog";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800496 asyncResp->res.jsonValue["@odata.type"] =
497 "#LogService.v1_1_0.LogService";
498 asyncResp->res.jsonValue["@odata.context"] =
499 "/redfish/v1/$metadata#LogService.LogService";
500 asyncResp->res.jsonValue["Name"] = "Event Log Service";
501 asyncResp->res.jsonValue["Description"] = "System Event Log Service";
502 asyncResp->res.jsonValue["Id"] = "Event Log";
503 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
504 asyncResp->res.jsonValue["Entries"] = {
505 {"@odata.id",
Ed Tanous029573d2019-02-01 10:57:49 -0800506 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}};
Gunnar Millse7d6c8b2019-07-03 11:30:01 -0500507 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = {
508
509 {"target", "/redfish/v1/Systems/system/LogServices/EventLog/"
510 "Actions/LogService.ClearLog"}};
Jason M. Bills489640c2019-05-17 09:56:36 -0700511 }
512};
513
Tim Lee1f56a3a2019-10-09 10:17:57 +0800514class JournalEventLogClear : public Node
Jason M. Bills489640c2019-05-17 09:56:36 -0700515{
516 public:
Tim Lee1f56a3a2019-10-09 10:17:57 +0800517 JournalEventLogClear(CrowApp &app) :
Jason M. Bills489640c2019-05-17 09:56:36 -0700518 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
519 "LogService.ClearLog/")
520 {
521 entityPrivileges = {
522 {boost::beast::http::verb::get, {{"Login"}}},
523 {boost::beast::http::verb::head, {{"Login"}}},
524 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
525 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
526 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
527 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
528 }
529
530 private:
531 void doPost(crow::Response &res, const crow::Request &req,
532 const std::vector<std::string> &params) override
533 {
534 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
535
536 // Clear the EventLog by deleting the log files
537 std::vector<std::filesystem::path> redfishLogFiles;
538 if (getRedfishLogFiles(redfishLogFiles))
539 {
540 for (const std::filesystem::path &file : redfishLogFiles)
541 {
542 std::error_code ec;
543 std::filesystem::remove(file, ec);
544 }
545 }
546
547 // Reload rsyslog so it knows to start new log files
548 crow::connections::systemBus->async_method_call(
549 [asyncResp](const boost::system::error_code ec) {
550 if (ec)
551 {
552 BMCWEB_LOG_ERROR << "Failed to reload rsyslog: " << ec;
553 messages::internalError(asyncResp->res);
554 return;
555 }
556
557 messages::success(asyncResp->res);
558 },
559 "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
560 "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service",
561 "replace");
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800562 }
563};
564
Jason M. Bills95820182019-04-22 16:25:34 -0700565static int fillEventLogEntryJson(const std::string &logEntryID,
566 const std::string logEntry,
567 nlohmann::json &logEntryJson)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800568{
Jason M. Bills95820182019-04-22 16:25:34 -0700569 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
Jason M. Billscd225da2019-05-08 15:31:57 -0700570 // First get the Timestamp
571 size_t space = logEntry.find_first_of(" ");
572 if (space == std::string::npos)
Jason M. Bills95820182019-04-22 16:25:34 -0700573 {
574 return 1;
575 }
Jason M. Billscd225da2019-05-08 15:31:57 -0700576 std::string timestamp = logEntry.substr(0, space);
577 // Then get the log contents
578 size_t entryStart = logEntry.find_first_not_of(" ", space);
579 if (entryStart == std::string::npos)
580 {
581 return 1;
582 }
583 std::string_view entry(logEntry);
584 entry.remove_prefix(entryStart);
585 // Use split to separate the entry into its fields
586 std::vector<std::string> logEntryFields;
587 boost::split(logEntryFields, entry, boost::is_any_of(","),
588 boost::token_compress_on);
589 // We need at least a MessageId to be valid
590 if (logEntryFields.size() < 1)
591 {
592 return 1;
593 }
594 std::string &messageID = logEntryFields[0];
Jason M. Bills95820182019-04-22 16:25:34 -0700595
Jason M. Bills4851d452019-03-28 11:27:48 -0700596 // Get the Message from the MessageRegistry
597 const message_registries::Message *message =
598 message_registries::getMessage(messageID);
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800599
Jason M. Bills4851d452019-03-28 11:27:48 -0700600 std::string msg;
601 std::string severity;
602 if (message != nullptr)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800603 {
Jason M. Bills4851d452019-03-28 11:27:48 -0700604 msg = message->message;
605 severity = message->severity;
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800606 }
607
Jason M. Bills15a86ff2019-06-18 13:49:54 -0700608 // Get the MessageArgs from the log if there are any
609 boost::beast::span<std::string> messageArgs;
610 if (logEntryFields.size() > 1)
Jason M. Bills4851d452019-03-28 11:27:48 -0700611 {
Jason M. Bills15a86ff2019-06-18 13:49:54 -0700612 std::string &messageArgsStart = logEntryFields[1];
613 // If the first string is empty, assume there are no MessageArgs
614 std::size_t messageArgsSize = 0;
615 if (!messageArgsStart.empty())
Jason M. Bills4851d452019-03-28 11:27:48 -0700616 {
Jason M. Bills15a86ff2019-06-18 13:49:54 -0700617 messageArgsSize = logEntryFields.size() - 1;
618 }
619
620 messageArgs = boost::beast::span(&messageArgsStart, messageArgsSize);
621
622 // Fill the MessageArgs into the Message
623 int i = 0;
624 for (const std::string &messageArg : messageArgs)
625 {
626 std::string argStr = "%" + std::to_string(++i);
627 size_t argPos = msg.find(argStr);
628 if (argPos != std::string::npos)
629 {
630 msg.replace(argPos, argStr.length(), messageArg);
631 }
Jason M. Bills4851d452019-03-28 11:27:48 -0700632 }
633 }
634
Jason M. Bills95820182019-04-22 16:25:34 -0700635 // Get the Created time from the timestamp. The log timestamp is in RFC3339
636 // format which matches the Redfish format except for the fractional seconds
637 // between the '.' and the '+', so just remove them.
638 std::size_t dot = timestamp.find_first_of(".");
639 std::size_t plus = timestamp.find_first_of("+");
640 if (dot != std::string::npos && plus != std::string::npos)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800641 {
Jason M. Bills95820182019-04-22 16:25:34 -0700642 timestamp.erase(dot, plus - dot);
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800643 }
644
645 // Fill in the log entry with the gathered data
Jason M. Bills95820182019-04-22 16:25:34 -0700646 logEntryJson = {
Andrew Geisslercb92c032018-08-17 07:56:14 -0700647 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800648 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Ed Tanous029573d2019-02-01 10:57:49 -0800649 {"@odata.id",
Jason M. Bills897967d2019-07-29 17:05:30 -0700650 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
Jason M. Bills95820182019-04-22 16:25:34 -0700651 logEntryID},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800652 {"Name", "System Event Log Entry"},
Jason M. Bills95820182019-04-22 16:25:34 -0700653 {"Id", logEntryID},
654 {"Message", std::move(msg)},
655 {"MessageId", std::move(messageID)},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800656 {"MessageArgs", std::move(messageArgs)},
657 {"EntryType", "Event"},
Jason M. Bills95820182019-04-22 16:25:34 -0700658 {"Severity", std::move(severity)},
659 {"Created", std::move(timestamp)}};
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800660 return 0;
661}
662
Anthony Wilson27062602019-04-22 02:10:09 -0500663class JournalEventLogEntryCollection : public Node
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800664{
665 public:
666 template <typename CrowApp>
Anthony Wilson27062602019-04-22 02:10:09 -0500667 JournalEventLogEntryCollection(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800668 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800669 {
670 entityPrivileges = {
671 {boost::beast::http::verb::get, {{"Login"}}},
672 {boost::beast::http::verb::head, {{"Login"}}},
673 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
674 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
675 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
676 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
677 }
678
679 private:
680 void doGet(crow::Response &res, const crow::Request &req,
681 const std::vector<std::string> &params) override
682 {
683 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous271584a2019-07-09 16:24:22 -0700684 uint64_t skip = 0;
685 uint64_t top = maxEntriesPerPage; // Show max entries by default
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800686 if (!getSkipParam(asyncResp->res, req, skip))
687 {
688 return;
689 }
690 if (!getTopParam(asyncResp->res, req, top))
691 {
692 return;
693 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800694 // Collections don't include the static data added by SubRoute because
695 // it has a duplicate entry for members
696 asyncResp->res.jsonValue["@odata.type"] =
697 "#LogEntryCollection.LogEntryCollection";
698 asyncResp->res.jsonValue["@odata.context"] =
699 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
700 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800701 "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800702 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
703 asyncResp->res.jsonValue["Description"] =
704 "Collection of System Event Log Entries";
Andrew Geisslercb92c032018-08-17 07:56:14 -0700705
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800706 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
707 logEntryArray = nlohmann::json::array();
Jason M. Bills95820182019-04-22 16:25:34 -0700708 // Go through the log files and create a unique ID for each entry
709 std::vector<std::filesystem::path> redfishLogFiles;
710 getRedfishLogFiles(redfishLogFiles);
Ed Tanousb01bf292019-03-25 19:25:26 +0000711 uint64_t entryCount = 0;
Jason M. Billscd225da2019-05-08 15:31:57 -0700712 std::string logEntry;
Jason M. Bills95820182019-04-22 16:25:34 -0700713
714 // Oldest logs are in the last file, so start there and loop backwards
Jason M. Billscd225da2019-05-08 15:31:57 -0700715 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend();
716 it++)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800717 {
Jason M. Billscd225da2019-05-08 15:31:57 -0700718 std::ifstream logStream(*it);
Jason M. Bills95820182019-04-22 16:25:34 -0700719 if (!logStream.is_open())
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800720 {
721 continue;
722 }
723
Jason M. Billse85d6b12019-07-29 17:01:15 -0700724 // Reset the unique ID on the first entry
725 bool firstEntry = true;
Jason M. Bills95820182019-04-22 16:25:34 -0700726 while (std::getline(logStream, logEntry))
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800727 {
Jason M. Bills95820182019-04-22 16:25:34 -0700728 entryCount++;
729 // Handle paging using skip (number of entries to skip from the
730 // start) and top (number of entries to display)
731 if (entryCount <= skip || entryCount > skip + top)
732 {
733 continue;
734 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800735
Jason M. Bills95820182019-04-22 16:25:34 -0700736 std::string idStr;
Jason M. Billse85d6b12019-07-29 17:01:15 -0700737 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
Jason M. Bills95820182019-04-22 16:25:34 -0700738 {
739 continue;
740 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800741
Jason M. Billse85d6b12019-07-29 17:01:15 -0700742 if (firstEntry)
743 {
744 firstEntry = false;
745 }
746
Jason M. Bills95820182019-04-22 16:25:34 -0700747 logEntryArray.push_back({});
748 nlohmann::json &bmcLogEntry = logEntryArray.back();
749 if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0)
750 {
751 messages::internalError(asyncResp->res);
752 return;
753 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800754 }
755 }
756 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
757 if (skip + top < entryCount)
758 {
759 asyncResp->res.jsonValue["Members@odata.nextLink"] =
Jason M. Bills95820182019-04-22 16:25:34 -0700760 "/redfish/v1/Systems/system/LogServices/EventLog/"
761 "Entries?$skip=" +
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800762 std::to_string(skip + top);
763 }
Anthony Wilson08a4e4b2019-04-12 08:23:05 -0500764 }
765};
766
Jason M. Bills897967d2019-07-29 17:05:30 -0700767class JournalEventLogEntry : public Node
768{
769 public:
770 JournalEventLogEntry(CrowApp &app) :
771 Node(app,
772 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/",
773 std::string())
774 {
775 entityPrivileges = {
776 {boost::beast::http::verb::get, {{"Login"}}},
777 {boost::beast::http::verb::head, {{"Login"}}},
778 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
779 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
780 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
781 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
782 }
783
784 private:
785 void doGet(crow::Response &res, const crow::Request &req,
786 const std::vector<std::string> &params) override
787 {
788 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
789 if (params.size() != 1)
790 {
791 messages::internalError(asyncResp->res);
792 return;
793 }
794 const std::string &targetID = params[0];
795
796 // Go through the log files and check the unique ID for each entry to
797 // find the target entry
798 std::vector<std::filesystem::path> redfishLogFiles;
799 getRedfishLogFiles(redfishLogFiles);
800 std::string logEntry;
801
802 // Oldest logs are in the last file, so start there and loop backwards
803 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend();
804 it++)
805 {
806 std::ifstream logStream(*it);
807 if (!logStream.is_open())
808 {
809 continue;
810 }
811
812 // Reset the unique ID on the first entry
813 bool firstEntry = true;
814 while (std::getline(logStream, logEntry))
815 {
816 std::string idStr;
817 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
818 {
819 continue;
820 }
821
822 if (firstEntry)
823 {
824 firstEntry = false;
825 }
826
827 if (idStr == targetID)
828 {
829 if (fillEventLogEntryJson(idStr, logEntry,
830 asyncResp->res.jsonValue) != 0)
831 {
832 messages::internalError(asyncResp->res);
833 return;
834 }
835 return;
836 }
837 }
838 }
839 // Requested ID was not found
840 messages::resourceMissingAtURI(asyncResp->res, targetID);
841 }
842};
843
Anthony Wilson08a4e4b2019-04-12 08:23:05 -0500844class DBusEventLogEntryCollection : public Node
845{
846 public:
847 template <typename CrowApp>
848 DBusEventLogEntryCollection(CrowApp &app) :
849 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
850 {
851 entityPrivileges = {
852 {boost::beast::http::verb::get, {{"Login"}}},
853 {boost::beast::http::verb::head, {{"Login"}}},
854 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
855 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
856 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
857 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
858 }
859
860 private:
861 void doGet(crow::Response &res, const crow::Request &req,
862 const std::vector<std::string> &params) override
863 {
864 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
865
866 // Collections don't include the static data added by SubRoute because
867 // it has a duplicate entry for members
868 asyncResp->res.jsonValue["@odata.type"] =
869 "#LogEntryCollection.LogEntryCollection";
870 asyncResp->res.jsonValue["@odata.context"] =
871 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
872 asyncResp->res.jsonValue["@odata.id"] =
873 "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
874 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
875 asyncResp->res.jsonValue["Description"] =
876 "Collection of System Event Log Entries";
877
Andrew Geisslercb92c032018-08-17 07:56:14 -0700878 // DBus implementation of EventLog/Entries
879 // Make call to Logging Service to find all log entry objects
880 crow::connections::systemBus->async_method_call(
881 [asyncResp](const boost::system::error_code ec,
882 GetManagedObjectsType &resp) {
883 if (ec)
884 {
885 // TODO Handle for specific error code
886 BMCWEB_LOG_ERROR
887 << "getLogEntriesIfaceData resp_handler got error "
888 << ec;
889 messages::internalError(asyncResp->res);
890 return;
891 }
892 nlohmann::json &entriesArray =
893 asyncResp->res.jsonValue["Members"];
894 entriesArray = nlohmann::json::array();
895 for (auto &objectPath : resp)
896 {
897 for (auto &interfaceMap : objectPath.second)
898 {
899 if (interfaceMap.first !=
900 "xyz.openbmc_project.Logging.Entry")
901 {
902 BMCWEB_LOG_DEBUG << "Bailing early on "
903 << interfaceMap.first;
904 continue;
905 }
906 entriesArray.push_back({});
907 nlohmann::json &thisEntry = entriesArray.back();
Ed Tanous66664f22019-10-11 13:05:49 -0700908 uint32_t *id = nullptr;
909 std::time_t timestamp{};
910 std::string *severity = nullptr;
911 std::string *message = nullptr;
Andrew Geisslercb92c032018-08-17 07:56:14 -0700912 for (auto &propertyMap : interfaceMap.second)
913 {
914 if (propertyMap.first == "Id")
915 {
916 id = sdbusplus::message::variant_ns::get_if<
917 uint32_t>(&propertyMap.second);
918 if (id == nullptr)
919 {
920 messages::propertyMissing(asyncResp->res,
921 "Id");
922 }
923 }
924 else if (propertyMap.first == "Timestamp")
925 {
926 const uint64_t *millisTimeStamp =
927 std::get_if<uint64_t>(&propertyMap.second);
928 if (millisTimeStamp == nullptr)
929 {
930 messages::propertyMissing(asyncResp->res,
931 "Timestamp");
Ed Tanous271584a2019-07-09 16:24:22 -0700932 continue;
Andrew Geisslercb92c032018-08-17 07:56:14 -0700933 }
934 // Retrieve Created property with format:
935 // yyyy-mm-ddThh:mm:ss
936 std::chrono::milliseconds chronoTimeStamp(
937 *millisTimeStamp);
Ed Tanous271584a2019-07-09 16:24:22 -0700938 timestamp = std::chrono::duration_cast<
939 std::chrono::duration<int>>(
940 chronoTimeStamp)
941 .count();
Andrew Geisslercb92c032018-08-17 07:56:14 -0700942 }
943 else if (propertyMap.first == "Severity")
944 {
945 severity = std::get_if<std::string>(
946 &propertyMap.second);
947 if (severity == nullptr)
948 {
949 messages::propertyMissing(asyncResp->res,
950 "Severity");
951 }
952 }
953 else if (propertyMap.first == "Message")
954 {
955 message = std::get_if<std::string>(
956 &propertyMap.second);
957 if (message == nullptr)
958 {
959 messages::propertyMissing(asyncResp->res,
960 "Message");
961 }
962 }
963 }
964 thisEntry = {
965 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
966 {"@odata.context", "/redfish/v1/"
967 "$metadata#LogEntry.LogEntry"},
968 {"@odata.id",
969 "/redfish/v1/Systems/system/LogServices/EventLog/"
970 "Entries/" +
971 std::to_string(*id)},
Anthony Wilson27062602019-04-22 02:10:09 -0500972 {"Name", "System Event Log Entry"},
Andrew Geisslercb92c032018-08-17 07:56:14 -0700973 {"Id", std::to_string(*id)},
974 {"Message", *message},
975 {"EntryType", "Event"},
976 {"Severity",
977 translateSeverityDbusToRedfish(*severity)},
978 {"Created", crow::utility::getDateTime(timestamp)}};
979 }
980 }
981 std::sort(entriesArray.begin(), entriesArray.end(),
982 [](const nlohmann::json &left,
983 const nlohmann::json &right) {
984 return (left["Id"] <= right["Id"]);
985 });
986 asyncResp->res.jsonValue["Members@odata.count"] =
987 entriesArray.size();
988 },
989 "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging",
990 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800991 }
992};
993
Anthony Wilson08a4e4b2019-04-12 08:23:05 -0500994class DBusEventLogEntry : public Node
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800995{
996 public:
Anthony Wilson08a4e4b2019-04-12 08:23:05 -0500997 DBusEventLogEntry(CrowApp &app) :
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800998 Node(app,
Ed Tanous029573d2019-02-01 10:57:49 -0800999 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/",
1000 std::string())
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001001 {
1002 entityPrivileges = {
1003 {boost::beast::http::verb::get, {{"Login"}}},
1004 {boost::beast::http::verb::head, {{"Login"}}},
1005 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1006 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1007 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1008 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1009 }
1010
1011 private:
1012 void doGet(crow::Response &res, const crow::Request &req,
1013 const std::vector<std::string> &params) override
1014 {
1015 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous029573d2019-02-01 10:57:49 -08001016 if (params.size() != 1)
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001017 {
1018 messages::internalError(asyncResp->res);
1019 return;
1020 }
Ed Tanous029573d2019-02-01 10:57:49 -08001021 const std::string &entryID = params[0];
Andrew Geisslercb92c032018-08-17 07:56:14 -07001022
Andrew Geisslercb92c032018-08-17 07:56:14 -07001023 // DBus implementation of EventLog/Entries
1024 // Make call to Logging Service to find all log entry objects
1025 crow::connections::systemBus->async_method_call(
1026 [asyncResp, entryID](const boost::system::error_code ec,
1027 GetManagedPropertyType &resp) {
1028 if (ec)
1029 {
1030 BMCWEB_LOG_ERROR
1031 << "EventLogEntry (DBus) resp_handler got error " << ec;
1032 messages::internalError(asyncResp->res);
1033 return;
1034 }
Ed Tanous66664f22019-10-11 13:05:49 -07001035 uint32_t *id = nullptr;
1036 std::time_t timestamp{};
1037 std::string *severity = nullptr;
1038 std::string *message = nullptr;
Andrew Geisslercb92c032018-08-17 07:56:14 -07001039 for (auto &propertyMap : resp)
1040 {
1041 if (propertyMap.first == "Id")
1042 {
1043 id = std::get_if<uint32_t>(&propertyMap.second);
1044 if (id == nullptr)
1045 {
1046 messages::propertyMissing(asyncResp->res, "Id");
1047 }
1048 }
1049 else if (propertyMap.first == "Timestamp")
1050 {
1051 const uint64_t *millisTimeStamp =
1052 std::get_if<uint64_t>(&propertyMap.second);
1053 if (millisTimeStamp == nullptr)
1054 {
1055 messages::propertyMissing(asyncResp->res,
1056 "Timestamp");
Ed Tanous271584a2019-07-09 16:24:22 -07001057 continue;
Andrew Geisslercb92c032018-08-17 07:56:14 -07001058 }
1059 // Retrieve Created property with format:
1060 // yyyy-mm-ddThh:mm:ss
1061 std::chrono::milliseconds chronoTimeStamp(
1062 *millisTimeStamp);
1063 timestamp =
Ed Tanous271584a2019-07-09 16:24:22 -07001064 std::chrono::duration_cast<
1065 std::chrono::duration<int>>(chronoTimeStamp)
Andrew Geisslercb92c032018-08-17 07:56:14 -07001066 .count();
1067 }
1068 else if (propertyMap.first == "Severity")
1069 {
1070 severity =
1071 std::get_if<std::string>(&propertyMap.second);
1072 if (severity == nullptr)
1073 {
1074 messages::propertyMissing(asyncResp->res,
1075 "Severity");
1076 }
1077 }
1078 else if (propertyMap.first == "Message")
1079 {
1080 message = std::get_if<std::string>(&propertyMap.second);
1081 if (message == nullptr)
1082 {
1083 messages::propertyMissing(asyncResp->res,
1084 "Message");
1085 }
1086 }
1087 }
Ed Tanous271584a2019-07-09 16:24:22 -07001088 if (id == nullptr || message == nullptr || severity == nullptr)
1089 {
1090 return;
1091 }
Andrew Geisslercb92c032018-08-17 07:56:14 -07001092 asyncResp->res.jsonValue = {
1093 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1094 {"@odata.context", "/redfish/v1/"
1095 "$metadata#LogEntry.LogEntry"},
1096 {"@odata.id",
1097 "/redfish/v1/Systems/system/LogServices/EventLog/"
1098 "Entries/" +
1099 std::to_string(*id)},
Anthony Wilson27062602019-04-22 02:10:09 -05001100 {"Name", "System Event Log Entry"},
Andrew Geisslercb92c032018-08-17 07:56:14 -07001101 {"Id", std::to_string(*id)},
1102 {"Message", *message},
1103 {"EntryType", "Event"},
1104 {"Severity", translateSeverityDbusToRedfish(*severity)},
Anthony Wilson08a4e4b2019-04-12 08:23:05 -05001105 {"Created", crow::utility::getDateTime(timestamp)}};
Andrew Geisslercb92c032018-08-17 07:56:14 -07001106 },
1107 "xyz.openbmc_project.Logging",
1108 "/xyz/openbmc_project/logging/entry/" + entryID,
1109 "org.freedesktop.DBus.Properties", "GetAll",
1110 "xyz.openbmc_project.Logging.Entry");
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001111 }
Chicago Duan336e96c2019-07-15 14:22:08 +08001112
1113 void doDelete(crow::Response &res, const crow::Request &req,
1114 const std::vector<std::string> &params) override
1115 {
1116
1117 BMCWEB_LOG_DEBUG << "Do delete single event entries.";
1118
1119 auto asyncResp = std::make_shared<AsyncResp>(res);
1120
1121 if (params.size() != 1)
1122 {
1123 messages::internalError(asyncResp->res);
1124 return;
1125 }
1126 std::string entryID = params[0];
1127
1128 dbus::utility::escapePathForDbus(entryID);
1129
1130 // Process response from Logging service.
1131 auto respHandler = [asyncResp](const boost::system::error_code ec) {
1132 BMCWEB_LOG_DEBUG << "EventLogEntry (DBus) doDelete callback: Done";
1133 if (ec)
1134 {
1135 // TODO Handle for specific error code
1136 BMCWEB_LOG_ERROR
1137 << "EventLogEntry (DBus) doDelete respHandler got error "
1138 << ec;
1139 asyncResp->res.result(
1140 boost::beast::http::status::internal_server_error);
1141 return;
1142 }
1143
1144 asyncResp->res.result(boost::beast::http::status::ok);
1145 };
1146
1147 // Make call to Logging service to request Delete Log
1148 crow::connections::systemBus->async_method_call(
1149 respHandler, "xyz.openbmc_project.Logging",
1150 "/xyz/openbmc_project/logging/entry/" + entryID,
1151 "xyz.openbmc_project.Object.Delete", "Delete");
1152 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001153};
1154
1155class BMCLogServiceCollection : public Node
1156{
1157 public:
1158 template <typename CrowApp>
1159 BMCLogServiceCollection(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001160 Node(app, "/redfish/v1/Managers/bmc/LogServices/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001161 {
Ed Tanous1da66f72018-07-27 16:13:37 -07001162 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001163 {boost::beast::http::verb::get, {{"Login"}}},
1164 {boost::beast::http::verb::head, {{"Login"}}},
1165 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1166 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1167 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1168 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001169 }
1170
1171 private:
1172 /**
1173 * Functions triggers appropriate requests on DBus
1174 */
1175 void doGet(crow::Response &res, const crow::Request &req,
1176 const std::vector<std::string> &params) override
1177 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001178 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001179 // Collections don't include the static data added by SubRoute because
1180 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -07001181 asyncResp->res.jsonValue["@odata.type"] =
Ed Tanous1da66f72018-07-27 16:13:37 -07001182 "#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -07001183 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001184 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -07001185 asyncResp->res.jsonValue["@odata.id"] =
1186 "/redfish/v1/Managers/bmc/LogServices";
1187 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
1188 asyncResp->res.jsonValue["Description"] =
Ed Tanous1da66f72018-07-27 16:13:37 -07001189 "Collection of LogServices for this Manager";
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001190 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
1191 logServiceArray = nlohmann::json::array();
1192#ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
1193 logServiceArray.push_back(
Anthony Wilson08a4e4b2019-04-12 08:23:05 -05001194 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}});
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001195#endif
Jason M. Billse1f26342018-07-18 12:12:00 -07001196 asyncResp->res.jsonValue["Members@odata.count"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001197 logServiceArray.size();
Ed Tanous1da66f72018-07-27 16:13:37 -07001198 }
1199};
1200
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001201class BMCJournalLogService : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001202{
1203 public:
1204 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001205 BMCJournalLogService(CrowApp &app) :
1206 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
Jason M. Billse1f26342018-07-18 12:12:00 -07001207 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001208 entityPrivileges = {
1209 {boost::beast::http::verb::get, {{"Login"}}},
1210 {boost::beast::http::verb::head, {{"Login"}}},
1211 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1212 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1213 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1214 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1215 }
1216
1217 private:
1218 void doGet(crow::Response &res, const crow::Request &req,
1219 const std::vector<std::string> &params) override
1220 {
1221 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001222 asyncResp->res.jsonValue["@odata.type"] =
1223 "#LogService.v1_1_0.LogService";
Ed Tanous0f74e642018-11-12 15:17:05 -08001224 asyncResp->res.jsonValue["@odata.id"] =
1225 "/redfish/v1/Managers/bmc/LogServices/Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -07001226 asyncResp->res.jsonValue["@odata.context"] =
1227 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001228 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
1229 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
1230 asyncResp->res.jsonValue["Id"] = "BMC Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -07001231 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
Jason M. Billscd50aa42019-02-12 17:09:02 -08001232 asyncResp->res.jsonValue["Entries"] = {
1233 {"@odata.id",
Ed Tanous086be232019-05-23 11:47:09 -07001234 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}};
Jason M. Billse1f26342018-07-18 12:12:00 -07001235 }
1236};
1237
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001238static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID,
1239 sd_journal *journal,
1240 nlohmann::json &bmcJournalLogEntryJson)
Jason M. Billse1f26342018-07-18 12:12:00 -07001241{
1242 // Get the Log Entry contents
1243 int ret = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -07001244
Ed Tanous39e77502019-03-04 17:35:53 -08001245 std::string_view msg;
Jason M. Bills16428a12018-11-02 12:42:29 -07001246 ret = getJournalMetadata(journal, "MESSAGE", msg);
Jason M. Billse1f26342018-07-18 12:12:00 -07001247 if (ret < 0)
1248 {
1249 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
1250 return 1;
1251 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001252
1253 // Get the severity from the PRIORITY field
Ed Tanous271584a2019-07-09 16:24:22 -07001254 long int severity = 8; // Default to an invalid priority
Jason M. Bills16428a12018-11-02 12:42:29 -07001255 ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
Jason M. Billse1f26342018-07-18 12:12:00 -07001256 if (ret < 0)
1257 {
1258 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
Jason M. Billse1f26342018-07-18 12:12:00 -07001259 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001260
1261 // Get the Created time from the timestamp
Jason M. Bills16428a12018-11-02 12:42:29 -07001262 std::string entryTimeStr;
1263 if (!getEntryTimestamp(journal, entryTimeStr))
Jason M. Billse1f26342018-07-18 12:12:00 -07001264 {
Jason M. Bills16428a12018-11-02 12:42:29 -07001265 return 1;
Jason M. Billse1f26342018-07-18 12:12:00 -07001266 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001267
1268 // Fill in the log entry with the gathered data
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001269 bmcJournalLogEntryJson = {
Andrew Geisslercb92c032018-08-17 07:56:14 -07001270 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
Jason M. Billse1f26342018-07-18 12:12:00 -07001271 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001272 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
1273 bmcJournalLogEntryID},
Jason M. Billse1f26342018-07-18 12:12:00 -07001274 {"Name", "BMC Journal Entry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001275 {"Id", bmcJournalLogEntryID},
Jason M. Bills16428a12018-11-02 12:42:29 -07001276 {"Message", msg},
Jason M. Billse1f26342018-07-18 12:12:00 -07001277 {"EntryType", "Oem"},
1278 {"Severity",
Jason M. Billsb6a61a52019-08-01 14:26:15 -07001279 severity <= 2 ? "Critical" : severity <= 4 ? "Warning" : "OK"},
Ed Tanous086be232019-05-23 11:47:09 -07001280 {"OemRecordFormat", "BMC Journal Entry"},
Jason M. Billse1f26342018-07-18 12:12:00 -07001281 {"Created", std::move(entryTimeStr)}};
1282 return 0;
1283}
1284
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001285class BMCJournalLogEntryCollection : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -07001286{
1287 public:
1288 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001289 BMCJournalLogEntryCollection(CrowApp &app) :
1290 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
Jason M. Billse1f26342018-07-18 12:12:00 -07001291 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001292 entityPrivileges = {
1293 {boost::beast::http::verb::get, {{"Login"}}},
1294 {boost::beast::http::verb::head, {{"Login"}}},
1295 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1296 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1297 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1298 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1299 }
1300
1301 private:
1302 void doGet(crow::Response &res, const crow::Request &req,
1303 const std::vector<std::string> &params) override
1304 {
1305 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001306 static constexpr const long maxEntriesPerPage = 1000;
Ed Tanous271584a2019-07-09 16:24:22 -07001307 uint64_t skip = 0;
1308 uint64_t top = maxEntriesPerPage; // Show max entries by default
Jason M. Bills16428a12018-11-02 12:42:29 -07001309 if (!getSkipParam(asyncResp->res, req, skip))
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001310 {
Jason M. Bills16428a12018-11-02 12:42:29 -07001311 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001312 }
Jason M. Bills16428a12018-11-02 12:42:29 -07001313 if (!getTopParam(asyncResp->res, req, top))
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001314 {
Jason M. Bills16428a12018-11-02 12:42:29 -07001315 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001316 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001317 // Collections don't include the static data added by SubRoute because
1318 // it has a duplicate entry for members
1319 asyncResp->res.jsonValue["@odata.type"] =
1320 "#LogEntryCollection.LogEntryCollection";
Ed Tanous0f74e642018-11-12 15:17:05 -08001321 asyncResp->res.jsonValue["@odata.id"] =
1322 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001323 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001324 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -07001325 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001326 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001327 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
1328 asyncResp->res.jsonValue["Description"] =
1329 "Collection of BMC Journal Entries";
Ed Tanous0f74e642018-11-12 15:17:05 -08001330 asyncResp->res.jsonValue["@odata.id"] =
1331 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001332 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1333 logEntryArray = nlohmann::json::array();
1334
1335 // Go through the journal and use the timestamp to create a unique ID
1336 // for each entry
1337 sd_journal *journalTmp = nullptr;
1338 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1339 if (ret < 0)
1340 {
1341 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -07001342 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001343 return;
1344 }
1345 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1346 journalTmp, sd_journal_close);
1347 journalTmp = nullptr;
Ed Tanousb01bf292019-03-25 19:25:26 +00001348 uint64_t entryCount = 0;
Jason M. Billse85d6b12019-07-29 17:01:15 -07001349 // Reset the unique ID on the first entry
1350 bool firstEntry = true;
Jason M. Billse1f26342018-07-18 12:12:00 -07001351 SD_JOURNAL_FOREACH(journal.get())
1352 {
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001353 entryCount++;
1354 // Handle paging using skip (number of entries to skip from the
1355 // start) and top (number of entries to display)
1356 if (entryCount <= skip || entryCount > skip + top)
1357 {
1358 continue;
1359 }
1360
Jason M. Bills16428a12018-11-02 12:42:29 -07001361 std::string idStr;
Jason M. Billse85d6b12019-07-29 17:01:15 -07001362 if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
Jason M. Billse1f26342018-07-18 12:12:00 -07001363 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001364 continue;
1365 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001366
Jason M. Billse85d6b12019-07-29 17:01:15 -07001367 if (firstEntry)
1368 {
1369 firstEntry = false;
1370 }
1371
Jason M. Billse1f26342018-07-18 12:12:00 -07001372 logEntryArray.push_back({});
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001373 nlohmann::json &bmcJournalLogEntry = logEntryArray.back();
1374 if (fillBMCJournalLogEntryJson(idStr, journal.get(),
1375 bmcJournalLogEntry) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -07001376 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001377 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001378 return;
1379 }
1380 }
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001381 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
1382 if (skip + top < entryCount)
1383 {
1384 asyncResp->res.jsonValue["Members@odata.nextLink"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001385 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
Jason M. Bills193ad2f2018-09-26 15:08:52 -07001386 std::to_string(skip + top);
1387 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001388 }
1389};
1390
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001391class BMCJournalLogEntry : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -07001392{
1393 public:
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001394 BMCJournalLogEntry(CrowApp &app) :
1395 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/",
Jason M. Billse1f26342018-07-18 12:12:00 -07001396 std::string())
1397 {
1398 entityPrivileges = {
1399 {boost::beast::http::verb::get, {{"Login"}}},
1400 {boost::beast::http::verb::head, {{"Login"}}},
1401 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1402 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1403 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1404 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1405 }
1406
1407 private:
1408 void doGet(crow::Response &res, const crow::Request &req,
1409 const std::vector<std::string> &params) override
1410 {
1411 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1412 if (params.size() != 1)
1413 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001414 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001415 return;
1416 }
Jason M. Bills16428a12018-11-02 12:42:29 -07001417 const std::string &entryID = params[0];
Jason M. Billse1f26342018-07-18 12:12:00 -07001418 // Convert the unique ID back to a timestamp to find the entry
Jason M. Billse1f26342018-07-18 12:12:00 -07001419 uint64_t ts = 0;
Ed Tanous271584a2019-07-09 16:24:22 -07001420 uint64_t index = 0;
Jason M. Bills16428a12018-11-02 12:42:29 -07001421 if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
Jason M. Billse1f26342018-07-18 12:12:00 -07001422 {
Jason M. Bills16428a12018-11-02 12:42:29 -07001423 return;
Jason M. Billse1f26342018-07-18 12:12:00 -07001424 }
1425
1426 sd_journal *journalTmp = nullptr;
1427 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1428 if (ret < 0)
1429 {
1430 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -07001431 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001432 return;
1433 }
1434 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1435 journalTmp, sd_journal_close);
1436 journalTmp = nullptr;
1437 // Go to the timestamp in the log and move to the entry at the index
Jason M. Billsaf07e3f2019-08-01 14:41:39 -07001438 // tracking the unique ID
1439 std::string idStr;
1440 bool firstEntry = true;
Jason M. Billse1f26342018-07-18 12:12:00 -07001441 ret = sd_journal_seek_realtime_usec(journal.get(), ts);
Ed Tanous271584a2019-07-09 16:24:22 -07001442 for (uint64_t i = 0; i <= index; i++)
Jason M. Billse1f26342018-07-18 12:12:00 -07001443 {
1444 sd_journal_next(journal.get());
Jason M. Billsaf07e3f2019-08-01 14:41:39 -07001445 if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
1446 {
1447 messages::internalError(asyncResp->res);
1448 return;
1449 }
1450 if (firstEntry)
1451 {
1452 firstEntry = false;
1453 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001454 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001455 // Confirm that the entry ID matches what was requested
Jason M. Billsaf07e3f2019-08-01 14:41:39 -07001456 if (idStr != entryID)
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001457 {
1458 messages::resourceMissingAtURI(asyncResp->res, entryID);
1459 return;
1460 }
1461
1462 if (fillBMCJournalLogEntryJson(entryID, journal.get(),
1463 asyncResp->res.jsonValue) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -07001464 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001465 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001466 return;
1467 }
1468 }
1469};
1470
Jason M. Bills424c4172019-03-21 13:50:33 -07001471class CrashdumpService : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -07001472{
1473 public:
1474 template <typename CrowApp>
Jason M. Bills424c4172019-03-21 13:50:33 -07001475 CrashdumpService(CrowApp &app) :
1476 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001477 {
Ed Tanous1da66f72018-07-27 16:13:37 -07001478 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001479 {boost::beast::http::verb::get, {{"Login"}}},
1480 {boost::beast::http::verb::head, {{"Login"}}},
1481 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1482 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1483 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1484 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001485 }
1486
1487 private:
1488 /**
1489 * Functions triggers appropriate requests on DBus
1490 */
1491 void doGet(crow::Response &res, const crow::Request &req,
1492 const std::vector<std::string> &params) override
1493 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001494 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001495 // Copy over the static data to include the entries added by SubRoute
Ed Tanous0f74e642018-11-12 15:17:05 -08001496 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Bills424c4172019-03-21 13:50:33 -07001497 "/redfish/v1/Systems/system/LogServices/Crashdump";
Jason M. Billse1f26342018-07-18 12:12:00 -07001498 asyncResp->res.jsonValue["@odata.type"] =
1499 "#LogService.v1_1_0.LogService";
1500 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001501 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Bills424c4172019-03-21 13:50:33 -07001502 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Service";
1503 asyncResp->res.jsonValue["Description"] = "Crashdump Service";
1504 asyncResp->res.jsonValue["Id"] = "Crashdump";
Jason M. Billse1f26342018-07-18 12:12:00 -07001505 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
1506 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
Jason M. Billscd50aa42019-02-12 17:09:02 -08001507 asyncResp->res.jsonValue["Entries"] = {
1508 {"@odata.id",
Jason M. Bills424c4172019-03-21 13:50:33 -07001509 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}};
Jason M. Billse1f26342018-07-18 12:12:00 -07001510 asyncResp->res.jsonValue["Actions"] = {
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001511 {"#LogService.ClearLog",
1512 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1513 "Actions/LogService.ClearLog"}}},
Ed Tanous1da66f72018-07-27 16:13:37 -07001514 {"Oem",
Jason M. Bills424c4172019-03-21 13:50:33 -07001515 {{"#Crashdump.OnDemand",
1516 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1517 "Actions/Oem/Crashdump.OnDemand"}}}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001518
1519#ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
Jason M. Billse1f26342018-07-18 12:12:00 -07001520 asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
Jason M. Bills424c4172019-03-21 13:50:33 -07001521 {"#Crashdump.SendRawPeci",
Anthony Wilson08a4e4b2019-04-12 08:23:05 -05001522 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1523 "Actions/Oem/Crashdump.SendRawPeci"}}});
Ed Tanous1da66f72018-07-27 16:13:37 -07001524#endif
Ed Tanous1da66f72018-07-27 16:13:37 -07001525 }
1526};
1527
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001528class CrashdumpClear : public Node
1529{
1530 public:
1531 CrashdumpClear(CrowApp &app) :
1532 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/"
1533 "LogService.ClearLog/")
1534 {
1535 entityPrivileges = {
1536 {boost::beast::http::verb::get, {{"Login"}}},
1537 {boost::beast::http::verb::head, {{"Login"}}},
1538 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1539 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1540 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1541 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1542 }
1543
1544 private:
1545 void doPost(crow::Response &res, const crow::Request &req,
1546 const std::vector<std::string> &params) override
1547 {
1548 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1549
1550 crow::connections::systemBus->async_method_call(
1551 [asyncResp](const boost::system::error_code ec,
1552 const std::string &resp) {
1553 if (ec)
1554 {
1555 messages::internalError(asyncResp->res);
1556 return;
1557 }
1558 messages::success(asyncResp->res);
1559 },
1560 crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll");
1561 }
1562};
1563
Jason M. Billse855dd22019-10-08 11:37:48 -07001564std::string getLogCreatedTime(const std::string &crashdump)
1565{
1566 nlohmann::json crashdumpJson =
1567 nlohmann::json::parse(crashdump, nullptr, false);
1568 if (crashdumpJson.is_discarded())
1569 {
1570 return std::string();
1571 }
1572
1573 nlohmann::json::const_iterator cdIt = crashdumpJson.find("crash_data");
1574 if (cdIt == crashdumpJson.end())
1575 {
1576 return std::string();
1577 }
1578
1579 nlohmann::json::const_iterator siIt = cdIt->find("METADATA");
1580 if (siIt == cdIt->end())
1581 {
1582 return std::string();
1583 }
1584
1585 nlohmann::json::const_iterator tsIt = siIt->find("timestamp");
1586 if (tsIt == siIt->end())
1587 {
1588 return std::string();
1589 }
1590
1591 const std::string *logTime = tsIt->get_ptr<const std::string *>();
1592 if (logTime == nullptr)
1593 {
1594 return std::string();
1595 }
1596
1597 std::string redfishDateTime = *logTime;
1598 if (redfishDateTime.length() > 2)
1599 {
1600 redfishDateTime.insert(redfishDateTime.end() - 2, ':');
1601 }
1602
1603 return redfishDateTime;
1604}
1605
1606std::string getLogFileName(const std::string &logTime)
1607{
1608 // Set the crashdump file name to "crashdump_<logTime>.json" using the
1609 // created time without the timezone info
1610 std::string fileTime = logTime;
1611 size_t plusPos = fileTime.rfind('+');
1612 if (plusPos != std::string::npos)
1613 {
1614 fileTime.erase(plusPos);
1615 }
1616 return "crashdump_" + fileTime + ".json";
1617}
1618
1619static void logCrashdumpEntry(std::shared_ptr<AsyncResp> asyncResp,
1620 const std::string &logID,
1621 nlohmann::json &logEntryJson)
1622{
1623 auto getStoredLogCallback = [asyncResp, logID, &logEntryJson](
1624 const boost::system::error_code ec,
1625 const std::variant<std::string> &resp) {
1626 if (ec)
1627 {
1628 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
Jason M. Bills1ddcf012019-11-26 14:59:21 -08001629 if (ec.value() ==
1630 boost::system::linux_error::bad_request_descriptor)
1631 {
1632 messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
1633 }
1634 else
1635 {
1636 messages::internalError(asyncResp->res);
1637 }
Jason M. Billse855dd22019-10-08 11:37:48 -07001638 return;
1639 }
1640 const std::string *log = std::get_if<std::string>(&resp);
1641 if (log == nullptr)
1642 {
1643 messages::internalError(asyncResp->res);
1644 return;
1645 }
1646 std::string logTime = getLogCreatedTime(*log);
1647 std::string fileName = getLogFileName(logTime);
1648
1649 logEntryJson = {
1650 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1651 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1652 {"@odata.id",
1653 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
1654 logID},
1655 {"Name", "CPU Crashdump"},
1656 {"Id", logID},
1657 {"EntryType", "Oem"},
1658 {"OemRecordFormat", "Crashdump URI"},
1659 {"Message",
1660 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
1661 logID + "/" + fileName},
1662 {"Created", std::move(logTime)}};
1663 };
1664 crow::connections::systemBus->async_method_call(
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001665 std::move(getStoredLogCallback), crashdumpObject,
1666 crashdumpPath + std::string("/") + logID,
1667 "org.freedesktop.DBus.Properties", "Get", crashdumpInterface, "Log");
Jason M. Billse855dd22019-10-08 11:37:48 -07001668}
1669
Jason M. Bills424c4172019-03-21 13:50:33 -07001670class CrashdumpEntryCollection : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001671{
1672 public:
1673 template <typename CrowApp>
Jason M. Bills424c4172019-03-21 13:50:33 -07001674 CrashdumpEntryCollection(CrowApp &app) :
1675 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001676 {
Ed Tanous1da66f72018-07-27 16:13:37 -07001677 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001678 {boost::beast::http::verb::get, {{"Login"}}},
1679 {boost::beast::http::verb::head, {{"Login"}}},
1680 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1681 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1682 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1683 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001684 }
1685
1686 private:
1687 /**
1688 * Functions triggers appropriate requests on DBus
1689 */
1690 void doGet(crow::Response &res, const crow::Request &req,
1691 const std::vector<std::string> &params) override
1692 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001693 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001694 // Collections don't include the static data added by SubRoute because
1695 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -07001696 auto getLogEntriesCallback = [asyncResp](
1697 const boost::system::error_code ec,
1698 const std::vector<std::string> &resp) {
1699 if (ec)
1700 {
1701 if (ec.value() !=
1702 boost::system::errc::no_such_file_or_directory)
Ed Tanous1da66f72018-07-27 16:13:37 -07001703 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001704 BMCWEB_LOG_DEBUG << "failed to get entries ec: "
1705 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001706 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001707 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07001708 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001709 }
1710 asyncResp->res.jsonValue["@odata.type"] =
1711 "#LogEntryCollection.LogEntryCollection";
Ed Tanous0f74e642018-11-12 15:17:05 -08001712 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Bills424c4172019-03-21 13:50:33 -07001713 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001714 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billse855dd22019-10-08 11:37:48 -07001715 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Bills424c4172019-03-21 13:50:33 -07001716 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001717 asyncResp->res.jsonValue["Description"] =
Jason M. Bills424c4172019-03-21 13:50:33 -07001718 "Collection of Crashdump Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001719 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1720 logEntryArray = nlohmann::json::array();
Jason M. Billse855dd22019-10-08 11:37:48 -07001721 std::vector<std::string> logIDs;
1722 // Get the list of log entries and build up an empty array big
1723 // enough to hold them
Jason M. Billse1f26342018-07-18 12:12:00 -07001724 for (const std::string &objpath : resp)
1725 {
Jason M. Billse855dd22019-10-08 11:37:48 -07001726 // Ignore the on-demand log
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001727 if (objpath.compare(crashdumpOnDemandPath) == 0)
Ed Tanous1da66f72018-07-27 16:13:37 -07001728 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001729 continue;
Ed Tanous1da66f72018-07-27 16:13:37 -07001730 }
Jason M. Billse855dd22019-10-08 11:37:48 -07001731
1732 // Get the log ID
Jason M. Billse1f26342018-07-18 12:12:00 -07001733 std::size_t lastPos = objpath.rfind("/");
Jason M. Billse855dd22019-10-08 11:37:48 -07001734 if (lastPos == std::string::npos)
Jason M. Billse1f26342018-07-18 12:12:00 -07001735 {
Jason M. Billse855dd22019-10-08 11:37:48 -07001736 continue;
Jason M. Billse1f26342018-07-18 12:12:00 -07001737 }
Jason M. Billse855dd22019-10-08 11:37:48 -07001738 logIDs.emplace_back(objpath.substr(lastPos + 1));
1739
1740 // Add a space for the log entry to the array
1741 logEntryArray.push_back({});
1742 }
1743 // Now go through and set up async calls to fill in the entries
1744 size_t index = 0;
1745 for (const std::string &logID : logIDs)
1746 {
1747 // Add the log entry to the array
1748 logCrashdumpEntry(asyncResp, logID, logEntryArray[index++]);
Jason M. Billse1f26342018-07-18 12:12:00 -07001749 }
1750 asyncResp->res.jsonValue["Members@odata.count"] =
1751 logEntryArray.size();
1752 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001753 crow::connections::systemBus->async_method_call(
1754 std::move(getLogEntriesCallback),
1755 "xyz.openbmc_project.ObjectMapper",
1756 "/xyz/openbmc_project/object_mapper",
1757 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001758 std::array<const char *, 1>{crashdumpInterface});
Ed Tanous1da66f72018-07-27 16:13:37 -07001759 }
1760};
1761
Jason M. Bills424c4172019-03-21 13:50:33 -07001762class CrashdumpEntry : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001763{
1764 public:
Jason M. Bills424c4172019-03-21 13:50:33 -07001765 CrashdumpEntry(CrowApp &app) :
Jason M. Billsd53dd412019-02-12 17:16:22 -08001766 Node(app,
Jason M. Bills424c4172019-03-21 13:50:33 -07001767 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/",
Ed Tanous1da66f72018-07-27 16:13:37 -07001768 std::string())
1769 {
1770 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001771 {boost::beast::http::verb::get, {{"Login"}}},
1772 {boost::beast::http::verb::head, {{"Login"}}},
1773 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1774 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1775 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1776 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001777 }
1778
1779 private:
1780 void doGet(crow::Response &res, const crow::Request &req,
1781 const std::vector<std::string> &params) override
1782 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001783 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001784 if (params.size() != 1)
1785 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001786 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001787 return;
1788 }
Jason M. Billse855dd22019-10-08 11:37:48 -07001789 const std::string &logID = params[0];
1790 logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue);
1791 }
1792};
1793
1794class CrashdumpFile : public Node
1795{
1796 public:
1797 CrashdumpFile(CrowApp &app) :
1798 Node(app,
1799 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/"
1800 "<str>/",
1801 std::string(), std::string())
1802 {
1803 entityPrivileges = {
1804 {boost::beast::http::verb::get, {{"Login"}}},
1805 {boost::beast::http::verb::head, {{"Login"}}},
1806 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1807 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1808 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1809 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1810 }
1811
1812 private:
1813 void doGet(crow::Response &res, const crow::Request &req,
1814 const std::vector<std::string> &params) override
1815 {
1816 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1817 if (params.size() != 2)
1818 {
1819 messages::internalError(asyncResp->res);
1820 return;
1821 }
1822 const std::string &logID = params[0];
1823 const std::string &fileName = params[1];
1824
1825 auto getStoredLogCallback = [asyncResp, logID, fileName](
Ed Tanousabf2add2019-01-22 16:40:12 -08001826 const boost::system::error_code ec,
1827 const std::variant<std::string> &resp) {
1828 if (ec)
1829 {
1830 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
1831 messages::internalError(asyncResp->res);
1832 return;
1833 }
1834 const std::string *log = std::get_if<std::string>(&resp);
1835 if (log == nullptr)
1836 {
1837 messages::internalError(asyncResp->res);
1838 return;
1839 }
Jason M. Billse855dd22019-10-08 11:37:48 -07001840
1841 // Verify the file name parameter is correct
1842 if (fileName != getLogFileName(getLogCreatedTime(*log)))
Ed Tanousabf2add2019-01-22 16:40:12 -08001843 {
Jason M. Billse855dd22019-10-08 11:37:48 -07001844 messages::resourceMissingAtURI(asyncResp->res, fileName);
Ed Tanousabf2add2019-01-22 16:40:12 -08001845 return;
1846 }
Jason M. Billse855dd22019-10-08 11:37:48 -07001847
1848 // Configure this to be a file download when accessed from a browser
1849 asyncResp->res.addHeader("Content-Disposition", "attachment");
1850 asyncResp->res.body() = *log;
Ed Tanousabf2add2019-01-22 16:40:12 -08001851 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001852 crow::connections::systemBus->async_method_call(
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001853 std::move(getStoredLogCallback), crashdumpObject,
1854 crashdumpPath + std::string("/") + logID,
1855 "org.freedesktop.DBus.Properties", "Get", crashdumpInterface,
Jason M. Bills424c4172019-03-21 13:50:33 -07001856 "Log");
Ed Tanous1da66f72018-07-27 16:13:37 -07001857 }
1858};
1859
Jason M. Bills424c4172019-03-21 13:50:33 -07001860class OnDemandCrashdump : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001861{
1862 public:
Jason M. Bills424c4172019-03-21 13:50:33 -07001863 OnDemandCrashdump(CrowApp &app) :
1864 Node(app,
1865 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
1866 "Crashdump.OnDemand/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001867 {
1868 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001869 {boost::beast::http::verb::get, {{"Login"}}},
1870 {boost::beast::http::verb::head, {{"Login"}}},
1871 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1872 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1873 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1874 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001875 }
1876
1877 private:
1878 void doPost(crow::Response &res, const crow::Request &req,
1879 const std::vector<std::string> &params) override
1880 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001881 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Bills48e46392019-02-13 12:58:37 -08001882 static std::unique_ptr<sdbusplus::bus::match::match> onDemandLogMatcher;
Ed Tanous1da66f72018-07-27 16:13:37 -07001883
Jason M. Bills48e46392019-02-13 12:58:37 -08001884 // Only allow one OnDemand Log request at a time
1885 if (onDemandLogMatcher != nullptr)
Ed Tanous1da66f72018-07-27 16:13:37 -07001886 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001887 messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
Ed Tanous1da66f72018-07-27 16:13:37 -07001888 return;
1889 }
1890 // Make this static so it survives outside this method
Ed Tanous271584a2019-07-09 16:24:22 -07001891 static boost::asio::steady_timer timeout(*req.ioService);
Ed Tanous1da66f72018-07-27 16:13:37 -07001892
Ed Tanous271584a2019-07-09 16:24:22 -07001893 timeout.expires_after(std::chrono::seconds(30));
Jason M. Billse1f26342018-07-18 12:12:00 -07001894 timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
Jason M. Bills48e46392019-02-13 12:58:37 -08001895 onDemandLogMatcher = nullptr;
Ed Tanous1da66f72018-07-27 16:13:37 -07001896 if (ec)
1897 {
1898 // operation_aborted is expected if timer is canceled before
1899 // completion.
1900 if (ec != boost::asio::error::operation_aborted)
1901 {
1902 BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
1903 }
1904 return;
1905 }
Jason M. Bills48e46392019-02-13 12:58:37 -08001906 BMCWEB_LOG_ERROR << "Timed out waiting for on-demand log";
Ed Tanous1da66f72018-07-27 16:13:37 -07001907
Jason M. Billsf12894f2018-10-09 12:45:45 -07001908 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001909 });
1910
Jason M. Bills3cf8ea32019-10-08 11:43:32 -07001911 auto onDemandLogMatcherCallback =
1912 [asyncResp](sdbusplus::message::message &m) {
1913 BMCWEB_LOG_DEBUG << "OnDemand log available match fired";
1914 timeout.cancel();
Ed Tanous271584a2019-07-09 16:24:22 -07001915
Jason M. Bills3cf8ea32019-10-08 11:43:32 -07001916 sdbusplus::message::object_path objPath;
1917 boost::container::flat_map<
1918 std::string, boost::container::flat_map<
1919 std::string, std::variant<std::string>>>
1920 interfacesAdded;
1921 m.read(objPath, interfacesAdded);
1922 const std::string *log = std::get_if<std::string>(
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001923 &interfacesAdded[crashdumpInterface]["Log"]);
Jason M. Bills3cf8ea32019-10-08 11:43:32 -07001924 if (log == nullptr)
1925 {
1926 messages::internalError(asyncResp->res);
1927 // Careful with onDemandLogMatcher. It is a unique_ptr to
1928 // the match object inside which this lambda is executing.
1929 // Once it is set to nullptr, the match object will be
1930 // destroyed and the lambda will lose its context, including
1931 // res, so it needs to be the last thing done.
1932 onDemandLogMatcher = nullptr;
1933 return;
1934 }
1935 nlohmann::json crashdumpJson =
1936 nlohmann::json::parse(*log, nullptr, false);
1937 if (crashdumpJson.is_discarded())
1938 {
1939 messages::internalError(asyncResp->res);
1940 // Careful with onDemandLogMatcher. It is a unique_ptr to
1941 // the match object inside which this lambda is executing.
1942 // Once it is set to nullptr, the match object will be
1943 // destroyed and the lambda will lose its context, including
1944 // res, so it needs to be the last thing done.
1945 onDemandLogMatcher = nullptr;
1946 return;
1947 }
1948 asyncResp->res.jsonValue = crashdumpJson;
Jason M. Bills48e46392019-02-13 12:58:37 -08001949 // Careful with onDemandLogMatcher. It is a unique_ptr to the
Ed Tanous1da66f72018-07-27 16:13:37 -07001950 // match object inside which this lambda is executing. Once it
1951 // is set to nullptr, the match object will be destroyed and the
1952 // lambda will lose its context, including res, so it needs to
1953 // be the last thing done.
Jason M. Bills48e46392019-02-13 12:58:37 -08001954 onDemandLogMatcher = nullptr;
Jason M. Bills3cf8ea32019-10-08 11:43:32 -07001955 };
Jason M. Bills48e46392019-02-13 12:58:37 -08001956 onDemandLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
Ed Tanous1da66f72018-07-27 16:13:37 -07001957 *crow::connections::systemBus,
1958 sdbusplus::bus::match::rules::interfacesAdded() +
Jason M. Bills424c4172019-03-21 13:50:33 -07001959 sdbusplus::bus::match::rules::argNpath(0,
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001960 crashdumpOnDemandPath),
Jason M. Bills48e46392019-02-13 12:58:37 -08001961 std::move(onDemandLogMatcherCallback));
Ed Tanous1da66f72018-07-27 16:13:37 -07001962
Jason M. Bills48e46392019-02-13 12:58:37 -08001963 auto generateonDemandLogCallback =
Jason M. Billse1f26342018-07-18 12:12:00 -07001964 [asyncResp](const boost::system::error_code ec,
1965 const std::string &resp) {
Ed Tanous1da66f72018-07-27 16:13:37 -07001966 if (ec)
1967 {
1968 if (ec.value() ==
1969 boost::system::errc::operation_not_supported)
1970 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001971 messages::resourceInStandby(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001972 }
Jason M. Bills4363d3b2019-11-26 15:04:29 -08001973 else if (ec.value() ==
1974 boost::system::errc::device_or_resource_busy)
1975 {
1976 messages::serviceTemporarilyUnavailable(asyncResp->res,
1977 "60");
1978 }
Ed Tanous1da66f72018-07-27 16:13:37 -07001979 else
1980 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001981 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001982 }
Ed Tanous271584a2019-07-09 16:24:22 -07001983
1984 timeout.cancel();
Jason M. Bills48e46392019-02-13 12:58:37 -08001985 onDemandLogMatcher = nullptr;
Ed Tanous1da66f72018-07-27 16:13:37 -07001986 return;
1987 }
1988 };
1989 crow::connections::systemBus->async_method_call(
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07001990 std::move(generateonDemandLogCallback), crashdumpObject,
1991 crashdumpPath, crashdumpOnDemandInterface, "GenerateOnDemandLog");
Ed Tanous1da66f72018-07-27 16:13:37 -07001992 }
1993};
1994
Jason M. Billse1f26342018-07-18 12:12:00 -07001995class SendRawPECI : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001996{
1997 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001998 SendRawPECI(CrowApp &app) :
Jason M. Bills424c4172019-03-21 13:50:33 -07001999 Node(app,
2000 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
2001 "Crashdump.SendRawPeci/")
Ed Tanous1da66f72018-07-27 16:13:37 -07002002 {
2003 entityPrivileges = {
2004 {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
2005 {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
2006 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
2007 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
2008 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
2009 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
2010 }
2011
2012 private:
2013 void doPost(crow::Response &res, const crow::Request &req,
2014 const std::vector<std::string> &params) override
2015 {
Jason M. Billse1f26342018-07-18 12:12:00 -07002016 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanousb1556422018-10-16 14:09:17 -07002017 uint8_t clientAddress = 0;
2018 uint8_t readLength = 0;
Ed Tanous1da66f72018-07-27 16:13:37 -07002019 std::vector<uint8_t> peciCommand;
Ed Tanousb1556422018-10-16 14:09:17 -07002020 if (!json_util::readJson(req, res, "ClientAddress", clientAddress,
2021 "ReadLength", readLength, "PECICommand",
2022 peciCommand))
Ed Tanous1da66f72018-07-27 16:13:37 -07002023 {
Ed Tanousb1556422018-10-16 14:09:17 -07002024 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07002025 }
Ed Tanousb1556422018-10-16 14:09:17 -07002026
Ed Tanous1da66f72018-07-27 16:13:37 -07002027 // Callback to return the Raw PECI response
Jason M. Billse1f26342018-07-18 12:12:00 -07002028 auto sendRawPECICallback =
2029 [asyncResp](const boost::system::error_code ec,
2030 const std::vector<uint8_t> &resp) {
2031 if (ec)
2032 {
2033 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
2034 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07002035 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07002036 return;
2037 }
2038 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
2039 {"PECIResponse", resp}};
2040 };
Ed Tanous1da66f72018-07-27 16:13:37 -07002041 // Call the SendRawPECI command with the provided data
2042 crow::connections::systemBus->async_method_call(
Jason M. Bills5b61b5e2019-10-16 10:59:02 -07002043 std::move(sendRawPECICallback), crashdumpObject, crashdumpPath,
2044 crashdumpRawPECIInterface, "SendRawPeci", clientAddress, readLength,
Ed Tanous4ed77cd2018-10-15 08:08:07 -07002045 peciCommand);
Ed Tanous1da66f72018-07-27 16:13:37 -07002046 }
2047};
2048
Andrew Geisslercb92c032018-08-17 07:56:14 -07002049/**
2050 * DBusLogServiceActionsClear class supports POST method for ClearLog action.
2051 */
2052class DBusLogServiceActionsClear : public Node
2053{
2054 public:
2055 DBusLogServiceActionsClear(CrowApp &app) :
2056 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
Tim Lee1f56a3a2019-10-09 10:17:57 +08002057 "LogService.ClearLog")
Andrew Geisslercb92c032018-08-17 07:56:14 -07002058 {
2059 entityPrivileges = {
2060 {boost::beast::http::verb::get, {{"Login"}}},
2061 {boost::beast::http::verb::head, {{"Login"}}},
2062 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2063 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2064 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2065 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2066 }
2067
2068 private:
2069 /**
2070 * Function handles POST method request.
2071 * The Clear Log actions does not require any parameter.The action deletes
2072 * all entries found in the Entries collection for this Log Service.
2073 */
2074 void doPost(crow::Response &res, const crow::Request &req,
2075 const std::vector<std::string> &params) override
2076 {
2077 BMCWEB_LOG_DEBUG << "Do delete all entries.";
2078
2079 auto asyncResp = std::make_shared<AsyncResp>(res);
2080 // Process response from Logging service.
2081 auto resp_handler = [asyncResp](const boost::system::error_code ec) {
2082 BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done";
2083 if (ec)
2084 {
2085 // TODO Handle for specific error code
2086 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec;
2087 asyncResp->res.result(
2088 boost::beast::http::status::internal_server_error);
2089 return;
2090 }
2091
2092 asyncResp->res.result(boost::beast::http::status::no_content);
2093 };
2094
2095 // Make call to Logging service to request Clear Log
2096 crow::connections::systemBus->async_method_call(
2097 resp_handler, "xyz.openbmc_project.Logging",
2098 "/xyz/openbmc_project/logging",
2099 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
2100 }
2101};
Ed Tanous1da66f72018-07-27 16:13:37 -07002102} // namespace redfish