blob: da4c5ab8bbdb8902e8ce139bb8a8df37d3ceb8f0 [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
James Feistf6150402019-01-08 10:36:20 -080018#include "filesystem.hpp"
Ed Tanous1da66f72018-07-27 16:13:37 -070019#include "node.hpp"
20
Jason M. Billse1f26342018-07-18 12:12:00 -070021#include <systemd/sd-journal.h>
22
Ed Tanous1da66f72018-07-27 16:13:37 -070023#include <boost/container/flat_map.hpp>
Jason M. Billse1f26342018-07-18 12:12:00 -070024#include <boost/utility/string_view.hpp>
Ed Tanousabf2add2019-01-22 16:40:12 -080025#include <variant>
Ed Tanous1da66f72018-07-27 16:13:37 -070026
27namespace redfish
28{
29
Ed Tanous4ed77cd2018-10-15 08:08:07 -070030constexpr char const *cpuLogObject = "com.intel.CpuDebugLog";
31constexpr char const *cpuLogPath = "/com/intel/CpuDebugLog";
32constexpr char const *cpuLogImmediatePath = "/com/intel/CpuDebugLog/Immediate";
33constexpr char const *cpuLogInterface = "com.intel.CpuDebugLog";
34constexpr char const *cpuLogImmediateInterface =
Ed Tanous1da66f72018-07-27 16:13:37 -070035 "com.intel.CpuDebugLog.Immediate";
Jason M. Billse1f26342018-07-18 12:12:00 -070036constexpr char const *cpuLogRawPECIInterface =
Ed Tanous1da66f72018-07-27 16:13:37 -070037 "com.intel.CpuDebugLog.SendRawPeci";
38
James Feistf6150402019-01-08 10:36:20 -080039namespace fs = std::filesystem;
Ed Tanous1da66f72018-07-27 16:13:37 -070040
Jason M. Bills16428a12018-11-02 12:42:29 -070041static int getJournalMetadata(sd_journal *journal,
42 const boost::string_view &field,
43 boost::string_view &contents)
44{
45 const char *data = nullptr;
46 size_t length = 0;
47 int ret = 0;
48 // Get the metadata from the requested field of the journal entry
49 ret = sd_journal_get_data(journal, field.data(), (const void **)&data,
50 &length);
51 if (ret < 0)
52 {
53 return ret;
54 }
55 contents = boost::string_view(data, length);
56 // Only use the content after the "=" character.
57 contents.remove_prefix(std::min(contents.find("=") + 1, contents.size()));
58 return ret;
59}
60
61static int getJournalMetadata(sd_journal *journal,
62 const boost::string_view &field, const int &base,
63 int &contents)
64{
65 int ret = 0;
66 boost::string_view metadata;
67 // Get the metadata from the requested field of the journal entry
68 ret = getJournalMetadata(journal, field, metadata);
69 if (ret < 0)
70 {
71 return ret;
72 }
73 contents = strtol(metadata.data(), nullptr, base);
74 return ret;
75}
76
77static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp)
78{
79 int ret = 0;
80 uint64_t timestamp = 0;
81 ret = sd_journal_get_realtime_usec(journal, &timestamp);
82 if (ret < 0)
83 {
84 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
85 << strerror(-ret);
86 return false;
87 }
88 time_t t =
89 static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s
90 struct tm *loctime = localtime(&t);
91 char entryTime[64] = {};
92 if (NULL != loctime)
93 {
94 strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime);
95 }
96 // Insert the ':' into the timezone
97 boost::string_view t1(entryTime);
98 boost::string_view t2(entryTime);
99 if (t1.size() > 2 && t2.size() > 2)
100 {
101 t1.remove_suffix(2);
102 t2.remove_prefix(t2.size() - 2);
103 }
104 entryTimestamp = t1.to_string() + ":" + t2.to_string();
105 return true;
106}
107
108static bool getSkipParam(crow::Response &res, const crow::Request &req,
109 long &skip)
110{
111 char *skipParam = req.urlParams.get("$skip");
112 if (skipParam != nullptr)
113 {
114 char *ptr = nullptr;
115 skip = std::strtol(skipParam, &ptr, 10);
116 if (*skipParam == '\0' || *ptr != '\0')
117 {
118
119 messages::queryParameterValueTypeError(res, std::string(skipParam),
120 "$skip");
121 return false;
122 }
123 if (skip < 0)
124 {
125
126 messages::queryParameterOutOfRange(res, std::to_string(skip),
127 "$skip", "greater than 0");
128 return false;
129 }
130 }
131 return true;
132}
133
134static constexpr const long maxEntriesPerPage = 1000;
135static bool getTopParam(crow::Response &res, const crow::Request &req,
136 long &top)
137{
138 char *topParam = req.urlParams.get("$top");
139 if (topParam != nullptr)
140 {
141 char *ptr = nullptr;
142 top = std::strtol(topParam, &ptr, 10);
143 if (*topParam == '\0' || *ptr != '\0')
144 {
145 messages::queryParameterValueTypeError(res, std::string(topParam),
146 "$top");
147 return false;
148 }
149 if (top < 1 || top > maxEntriesPerPage)
150 {
151
152 messages::queryParameterOutOfRange(
153 res, std::to_string(top), "$top",
154 "1-" + std::to_string(maxEntriesPerPage));
155 return false;
156 }
157 }
158 return true;
159}
160
161static bool getUniqueEntryID(sd_journal *journal, std::string &entryID)
162{
163 int ret = 0;
164 static uint64_t prevTs = 0;
165 static int index = 0;
166 // Get the entry timestamp
167 uint64_t curTs = 0;
168 ret = sd_journal_get_realtime_usec(journal, &curTs);
169 if (ret < 0)
170 {
171 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
172 << strerror(-ret);
173 return false;
174 }
175 // If the timestamp isn't unique, increment the index
176 if (curTs == prevTs)
177 {
178 index++;
179 }
180 else
181 {
182 // Otherwise, reset it
183 index = 0;
184 }
185 // Save the timestamp
186 prevTs = curTs;
187
188 entryID = std::to_string(curTs);
189 if (index > 0)
190 {
191 entryID += "_" + std::to_string(index);
192 }
193 return true;
194}
195
196static bool getTimestampFromID(crow::Response &res, const std::string &entryID,
197 uint64_t &timestamp, uint16_t &index)
198{
199 if (entryID.empty())
200 {
201 return false;
202 }
203 // Convert the unique ID back to a timestamp to find the entry
204 boost::string_view tsStr(entryID);
205
206 auto underscorePos = tsStr.find("_");
207 if (underscorePos != tsStr.npos)
208 {
209 // Timestamp has an index
210 tsStr.remove_suffix(tsStr.size() - underscorePos);
211 boost::string_view indexStr(entryID);
212 indexStr.remove_prefix(underscorePos + 1);
213 std::size_t pos;
214 try
215 {
216 index = std::stoul(indexStr.to_string(), &pos);
217 }
218 catch (std::invalid_argument)
219 {
220 messages::resourceMissingAtURI(res, entryID);
221 return false;
222 }
223 catch (std::out_of_range)
224 {
225 messages::resourceMissingAtURI(res, entryID);
226 return false;
227 }
228 if (pos != indexStr.size())
229 {
230 messages::resourceMissingAtURI(res, entryID);
231 return false;
232 }
233 }
234 // Timestamp has no index
235 std::size_t pos;
236 try
237 {
238 timestamp = std::stoull(tsStr.to_string(), &pos);
239 }
240 catch (std::invalid_argument)
241 {
242 messages::resourceMissingAtURI(res, entryID);
243 return false;
244 }
245 catch (std::out_of_range)
246 {
247 messages::resourceMissingAtURI(res, entryID);
248 return false;
249 }
250 if (pos != tsStr.size())
251 {
252 messages::resourceMissingAtURI(res, entryID);
253 return false;
254 }
255 return true;
256}
257
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800258class SystemLogServiceCollection : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700259{
260 public:
261 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800262 SystemLogServiceCollection(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800263 Node(app, "/redfish/v1/Systems/system/LogServices/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800264 {
265 entityPrivileges = {
266 {boost::beast::http::verb::get, {{"Login"}}},
267 {boost::beast::http::verb::head, {{"Login"}}},
268 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
269 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
270 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
271 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
272 }
273
274 private:
275 /**
276 * Functions triggers appropriate requests on DBus
277 */
278 void doGet(crow::Response &res, const crow::Request &req,
279 const std::vector<std::string> &params) override
280 {
281 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800282 // Collections don't include the static data added by SubRoute because
283 // it has a duplicate entry for members
284 asyncResp->res.jsonValue["@odata.type"] =
285 "#LogServiceCollection.LogServiceCollection";
286 asyncResp->res.jsonValue["@odata.context"] =
287 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
288 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800289 "/redfish/v1/Systems/system/LogServices";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800290 asyncResp->res.jsonValue["Name"] = "System Log Services Collection";
291 asyncResp->res.jsonValue["Description"] =
292 "Collection of LogServices for this Computer System";
293 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
294 logServiceArray = nlohmann::json::array();
Ed Tanous029573d2019-02-01 10:57:49 -0800295 logServiceArray.push_back(
296 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}});
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800297 asyncResp->res.jsonValue["Members@odata.count"] =
298 logServiceArray.size();
299 }
300};
301
302class EventLogService : public Node
303{
304 public:
305 template <typename CrowApp>
306 EventLogService(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800307 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800308 {
309 entityPrivileges = {
310 {boost::beast::http::verb::get, {{"Login"}}},
311 {boost::beast::http::verb::head, {{"Login"}}},
312 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
313 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
314 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
315 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
316 }
317
318 private:
319 void doGet(crow::Response &res, const crow::Request &req,
320 const std::vector<std::string> &params) override
321 {
322 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
323
324 const std::string &name = params[0];
325 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800326 "/redfish/v1/Systems/system/LogServices/EventLog";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800327 asyncResp->res.jsonValue["@odata.type"] =
328 "#LogService.v1_1_0.LogService";
329 asyncResp->res.jsonValue["@odata.context"] =
330 "/redfish/v1/$metadata#LogService.LogService";
331 asyncResp->res.jsonValue["Name"] = "Event Log Service";
332 asyncResp->res.jsonValue["Description"] = "System Event Log Service";
333 asyncResp->res.jsonValue["Id"] = "Event Log";
334 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
335 asyncResp->res.jsonValue["Entries"] = {
336 {"@odata.id",
Ed Tanous029573d2019-02-01 10:57:49 -0800337 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}};
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800338 }
339};
340
Ed Tanous029573d2019-02-01 10:57:49 -0800341static int fillEventLogEntryJson(const std::string &bmcLogEntryID,
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800342 const boost::string_view &messageID,
343 sd_journal *journal,
344 nlohmann::json &bmcLogEntryJson)
345{
346 // Get the Log Entry contents
347 int ret = 0;
348
349 boost::string_view msg;
350 ret = getJournalMetadata(journal, "MESSAGE", msg);
351 if (ret < 0)
352 {
353 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
354 return 1;
355 }
356
357 // Get the severity from the PRIORITY field
358 int severity = 8; // Default to an invalid priority
359 ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
360 if (ret < 0)
361 {
362 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
363 return 1;
364 }
365
366 // Get the MessageArgs from the journal entry by finding all of the
367 // REDFISH_MESSAGE_ARG_x fields
368 const void *data;
369 size_t length;
370 std::vector<std::string> messageArgs;
371 SD_JOURNAL_FOREACH_DATA(journal, data, length)
372 {
373 boost::string_view field(static_cast<const char *>(data), length);
374 if (field.starts_with("REDFISH_MESSAGE_ARG_"))
375 {
376 // Get the Arg number from the field name
377 field.remove_prefix(sizeof("REDFISH_MESSAGE_ARG_") - 1);
378 if (field.empty())
379 {
380 continue;
381 }
382 int argNum = std::strtoul(field.data(), nullptr, 10);
383 if (argNum == 0)
384 {
385 continue;
386 }
387 // Get the Arg value after the "=" character.
388 field.remove_prefix(std::min(field.find("=") + 1, field.size()));
389 // Make sure we have enough space in messageArgs
390 if (argNum > messageArgs.size())
391 {
392 messageArgs.resize(argNum);
393 }
394 messageArgs[argNum - 1] = field.to_string();
395 }
396 }
397
398 // Get the Created time from the timestamp
399 std::string entryTimeStr;
400 if (!getEntryTimestamp(journal, entryTimeStr))
401 {
402 return 1;
403 }
404
405 // Fill in the log entry with the gathered data
406 bmcLogEntryJson = {
407 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
408 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Ed Tanous029573d2019-02-01 10:57:49 -0800409 {"@odata.id",
410 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
411 bmcLogEntryID},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800412 {"Name", "System Event Log Entry"},
413 {"Id", bmcLogEntryID},
414 {"Message", msg},
415 {"MessageId", messageID},
416 {"MessageArgs", std::move(messageArgs)},
417 {"EntryType", "Event"},
418 {"Severity",
419 severity <= 2 ? "Critical"
420 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
421 {"Created", std::move(entryTimeStr)}};
422 return 0;
423}
424
425class EventLogEntryCollection : public Node
426{
427 public:
428 template <typename CrowApp>
429 EventLogEntryCollection(CrowApp &app) :
Ed Tanous029573d2019-02-01 10:57:49 -0800430 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800431 {
432 entityPrivileges = {
433 {boost::beast::http::verb::get, {{"Login"}}},
434 {boost::beast::http::verb::head, {{"Login"}}},
435 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
436 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
437 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
438 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
439 }
440
441 private:
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);
446 long skip = 0;
447 long top = maxEntriesPerPage; // Show max entries by default
448 if (!getSkipParam(asyncResp->res, req, skip))
449 {
450 return;
451 }
452 if (!getTopParam(asyncResp->res, req, top))
453 {
454 return;
455 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800456 // Collections don't include the static data added by SubRoute because
457 // it has a duplicate entry for members
458 asyncResp->res.jsonValue["@odata.type"] =
459 "#LogEntryCollection.LogEntryCollection";
460 asyncResp->res.jsonValue["@odata.context"] =
461 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
462 asyncResp->res.jsonValue["@odata.id"] =
Ed Tanous029573d2019-02-01 10:57:49 -0800463 "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800464 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
465 asyncResp->res.jsonValue["Description"] =
466 "Collection of System Event Log Entries";
467 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
468 logEntryArray = nlohmann::json::array();
469
470 // Go through the journal and create a unique ID for each entry
471 sd_journal *journalTmp = nullptr;
472 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
473 if (ret < 0)
474 {
475 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
476 messages::internalError(asyncResp->res);
477 return;
478 }
479 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
480 journalTmp, sd_journal_close);
481 journalTmp = nullptr;
482 uint64_t entryCount = 0;
483 SD_JOURNAL_FOREACH(journal.get())
484 {
485 // Look for only journal entries that contain a REDFISH_MESSAGE_ID
486 // field
487 boost::string_view messageID;
488 ret = getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID",
489 messageID);
490 if (ret < 0)
491 {
492 continue;
493 }
494
495 entryCount++;
496 // Handle paging using skip (number of entries to skip from the
497 // start) and top (number of entries to display)
498 if (entryCount <= skip || entryCount > skip + top)
499 {
500 continue;
501 }
502
503 std::string idStr;
504 if (!getUniqueEntryID(journal.get(), idStr))
505 {
506 continue;
507 }
508
509 logEntryArray.push_back({});
510 nlohmann::json &bmcLogEntry = logEntryArray.back();
Ed Tanous029573d2019-02-01 10:57:49 -0800511 if (fillEventLogEntryJson(idStr, messageID, journal.get(),
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800512 bmcLogEntry) != 0)
513 {
514 messages::internalError(asyncResp->res);
515 return;
516 }
517 }
518 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
519 if (skip + top < entryCount)
520 {
521 asyncResp->res.jsonValue["Members@odata.nextLink"] =
522 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries?$skip=" +
523 std::to_string(skip + top);
524 }
525 }
526};
527
528class EventLogEntry : public Node
529{
530 public:
531 EventLogEntry(CrowApp &app) :
532 Node(app,
Ed Tanous029573d2019-02-01 10:57:49 -0800533 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/",
534 std::string())
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800535 {
536 entityPrivileges = {
537 {boost::beast::http::verb::get, {{"Login"}}},
538 {boost::beast::http::verb::head, {{"Login"}}},
539 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
540 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
541 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
542 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
543 }
544
545 private:
546 void doGet(crow::Response &res, const crow::Request &req,
547 const std::vector<std::string> &params) override
548 {
549 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous029573d2019-02-01 10:57:49 -0800550 if (params.size() != 1)
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800551 {
552 messages::internalError(asyncResp->res);
553 return;
554 }
Ed Tanous029573d2019-02-01 10:57:49 -0800555 const std::string &entryID = params[0];
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800556 // Convert the unique ID back to a timestamp to find the entry
557 uint64_t ts = 0;
558 uint16_t index = 0;
559 if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
560 {
561 return;
562 }
563
564 sd_journal *journalTmp = nullptr;
565 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
566 if (ret < 0)
567 {
568 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
569 messages::internalError(asyncResp->res);
570 return;
571 }
572 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
573 journalTmp, sd_journal_close);
574 journalTmp = nullptr;
575 // Go to the timestamp in the log and move to the entry at the index
576 ret = sd_journal_seek_realtime_usec(journal.get(), ts);
577 for (int i = 0; i <= index; i++)
578 {
579 sd_journal_next(journal.get());
580 }
581 // Confirm that the entry ID matches what was requested
582 std::string idStr;
583 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
584 {
585 messages::resourceMissingAtURI(asyncResp->res, entryID);
586 return;
587 }
588
589 // only use journal entries that contain a REDFISH_MESSAGE_ID
590 // field
591 boost::string_view messageID;
592 ret =
593 getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", messageID);
594 if (ret < 0)
595 {
Ed Tanous029573d2019-02-01 10:57:49 -0800596 messages::resourceNotFound(asyncResp->res, "LogEntry", "system");
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800597 return;
598 }
599
Ed Tanous029573d2019-02-01 10:57:49 -0800600 if (fillEventLogEntryJson(entryID, messageID, journal.get(),
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800601 asyncResp->res.jsonValue) != 0)
602 {
603 messages::internalError(asyncResp->res);
604 return;
605 }
606 }
607};
608
609class BMCLogServiceCollection : public Node
610{
611 public:
612 template <typename CrowApp>
613 BMCLogServiceCollection(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700614 Node(app, "/redfish/v1/Managers/bmc/LogServices/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700615 {
Ed Tanous1da66f72018-07-27 16:13:37 -0700616 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700617 {boost::beast::http::verb::get, {{"Login"}}},
618 {boost::beast::http::verb::head, {{"Login"}}},
619 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
620 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
621 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
622 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700623 }
624
625 private:
626 /**
627 * Functions triggers appropriate requests on DBus
628 */
629 void doGet(crow::Response &res, const crow::Request &req,
630 const std::vector<std::string> &params) override
631 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700632 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700633 // Collections don't include the static data added by SubRoute because
634 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -0700635 asyncResp->res.jsonValue["@odata.type"] =
Ed Tanous1da66f72018-07-27 16:13:37 -0700636 "#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700637 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800638 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700639 asyncResp->res.jsonValue["@odata.id"] =
640 "/redfish/v1/Managers/bmc/LogServices";
641 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
642 asyncResp->res.jsonValue["Description"] =
Ed Tanous1da66f72018-07-27 16:13:37 -0700643 "Collection of LogServices for this Manager";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800644 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
645 logServiceArray = nlohmann::json::array();
646#ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
647 logServiceArray.push_back(
648 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}});
649#endif
Ed Tanous1da66f72018-07-27 16:13:37 -0700650#ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800651 logServiceArray.push_back(
Ed Tanous4ed77cd2018-10-15 08:08:07 -0700652 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/CpuLog"}});
Ed Tanous1da66f72018-07-27 16:13:37 -0700653#endif
Jason M. Billse1f26342018-07-18 12:12:00 -0700654 asyncResp->res.jsonValue["Members@odata.count"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800655 logServiceArray.size();
Ed Tanous1da66f72018-07-27 16:13:37 -0700656 }
657};
658
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800659class BMCJournalLogService : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700660{
661 public:
662 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800663 BMCJournalLogService(CrowApp &app) :
664 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
Jason M. Billse1f26342018-07-18 12:12:00 -0700665 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700666 entityPrivileges = {
667 {boost::beast::http::verb::get, {{"Login"}}},
668 {boost::beast::http::verb::head, {{"Login"}}},
669 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
670 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
671 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
672 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
673 }
674
675 private:
676 void doGet(crow::Response &res, const crow::Request &req,
677 const std::vector<std::string> &params) override
678 {
679 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700680 asyncResp->res.jsonValue["@odata.type"] =
681 "#LogService.v1_1_0.LogService";
Ed Tanous0f74e642018-11-12 15:17:05 -0800682 asyncResp->res.jsonValue["@odata.id"] =
683 "/redfish/v1/Managers/bmc/LogServices/Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -0700684 asyncResp->res.jsonValue["@odata.context"] =
685 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800686 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
687 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
688 asyncResp->res.jsonValue["Id"] = "BMC Journal";
Jason M. Billse1f26342018-07-18 12:12:00 -0700689 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
690 }
691};
692
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800693static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID,
694 sd_journal *journal,
695 nlohmann::json &bmcJournalLogEntryJson)
Jason M. Billse1f26342018-07-18 12:12:00 -0700696{
697 // Get the Log Entry contents
698 int ret = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -0700699
Jason M. Bills16428a12018-11-02 12:42:29 -0700700 boost::string_view msg;
701 ret = getJournalMetadata(journal, "MESSAGE", msg);
Jason M. Billse1f26342018-07-18 12:12:00 -0700702 if (ret < 0)
703 {
704 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
705 return 1;
706 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700707
708 // Get the severity from the PRIORITY field
Jason M. Billse1f26342018-07-18 12:12:00 -0700709 int severity = 8; // Default to an invalid priority
Jason M. Bills16428a12018-11-02 12:42:29 -0700710 ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
Jason M. Billse1f26342018-07-18 12:12:00 -0700711 if (ret < 0)
712 {
713 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
714 return 1;
715 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700716
717 // Get the Created time from the timestamp
Jason M. Bills16428a12018-11-02 12:42:29 -0700718 std::string entryTimeStr;
719 if (!getEntryTimestamp(journal, entryTimeStr))
Jason M. Billse1f26342018-07-18 12:12:00 -0700720 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700721 return 1;
Jason M. Billse1f26342018-07-18 12:12:00 -0700722 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700723
724 // Fill in the log entry with the gathered data
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800725 bmcJournalLogEntryJson = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700726 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
727 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800728 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
729 bmcJournalLogEntryID},
Jason M. Billse1f26342018-07-18 12:12:00 -0700730 {"Name", "BMC Journal Entry"},
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800731 {"Id", bmcJournalLogEntryID},
Jason M. Bills16428a12018-11-02 12:42:29 -0700732 {"Message", msg},
Jason M. Billse1f26342018-07-18 12:12:00 -0700733 {"EntryType", "Oem"},
734 {"Severity",
735 severity <= 2 ? "Critical"
736 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
737 {"OemRecordFormat", "Intel BMC Journal Entry"},
738 {"Created", std::move(entryTimeStr)}};
739 return 0;
740}
741
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800742class BMCJournalLogEntryCollection : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -0700743{
744 public:
745 template <typename CrowApp>
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800746 BMCJournalLogEntryCollection(CrowApp &app) :
747 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
Jason M. Billse1f26342018-07-18 12:12:00 -0700748 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700749 entityPrivileges = {
750 {boost::beast::http::verb::get, {{"Login"}}},
751 {boost::beast::http::verb::head, {{"Login"}}},
752 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
753 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
754 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
755 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
756 }
757
758 private:
759 void doGet(crow::Response &res, const crow::Request &req,
760 const std::vector<std::string> &params) override
761 {
762 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700763 static constexpr const long maxEntriesPerPage = 1000;
764 long skip = 0;
765 long top = maxEntriesPerPage; // Show max entries by default
Jason M. Bills16428a12018-11-02 12:42:29 -0700766 if (!getSkipParam(asyncResp->res, req, skip))
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700767 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700768 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700769 }
Jason M. Bills16428a12018-11-02 12:42:29 -0700770 if (!getTopParam(asyncResp->res, req, top))
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700771 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700772 return;
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700773 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700774 // Collections don't include the static data added by SubRoute because
775 // it has a duplicate entry for members
776 asyncResp->res.jsonValue["@odata.type"] =
777 "#LogEntryCollection.LogEntryCollection";
Ed Tanous0f74e642018-11-12 15:17:05 -0800778 asyncResp->res.jsonValue["@odata.id"] =
779 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -0700780 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800781 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -0700782 asyncResp->res.jsonValue["@odata.id"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800783 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -0700784 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
785 asyncResp->res.jsonValue["Description"] =
786 "Collection of BMC Journal Entries";
Ed Tanous0f74e642018-11-12 15:17:05 -0800787 asyncResp->res.jsonValue["@odata.id"] =
788 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -0700789 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
790 logEntryArray = nlohmann::json::array();
791
792 // Go through the journal and use the timestamp to create a unique ID
793 // for each entry
794 sd_journal *journalTmp = nullptr;
795 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
796 if (ret < 0)
797 {
798 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -0700799 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700800 return;
801 }
802 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
803 journalTmp, sd_journal_close);
804 journalTmp = nullptr;
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700805 uint64_t entryCount = 0;
Jason M. Billse1f26342018-07-18 12:12:00 -0700806 SD_JOURNAL_FOREACH(journal.get())
807 {
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700808 entryCount++;
809 // Handle paging using skip (number of entries to skip from the
810 // start) and top (number of entries to display)
811 if (entryCount <= skip || entryCount > skip + top)
812 {
813 continue;
814 }
815
Jason M. Bills16428a12018-11-02 12:42:29 -0700816 std::string idStr;
817 if (!getUniqueEntryID(journal.get(), idStr))
Jason M. Billse1f26342018-07-18 12:12:00 -0700818 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700819 continue;
820 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700821
Jason M. Billse1f26342018-07-18 12:12:00 -0700822 logEntryArray.push_back({});
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800823 nlohmann::json &bmcJournalLogEntry = logEntryArray.back();
824 if (fillBMCJournalLogEntryJson(idStr, journal.get(),
825 bmcJournalLogEntry) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -0700826 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700827 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700828 return;
829 }
830 }
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700831 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
832 if (skip + top < entryCount)
833 {
834 asyncResp->res.jsonValue["Members@odata.nextLink"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800835 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
Jason M. Bills193ad2f2018-09-26 15:08:52 -0700836 std::to_string(skip + top);
837 }
Jason M. Billse1f26342018-07-18 12:12:00 -0700838 }
839};
840
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800841class BMCJournalLogEntry : public Node
Jason M. Billse1f26342018-07-18 12:12:00 -0700842{
843 public:
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800844 BMCJournalLogEntry(CrowApp &app) :
845 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/",
Jason M. Billse1f26342018-07-18 12:12:00 -0700846 std::string())
847 {
848 entityPrivileges = {
849 {boost::beast::http::verb::get, {{"Login"}}},
850 {boost::beast::http::verb::head, {{"Login"}}},
851 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
852 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
853 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
854 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
855 }
856
857 private:
858 void doGet(crow::Response &res, const crow::Request &req,
859 const std::vector<std::string> &params) override
860 {
861 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
862 if (params.size() != 1)
863 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700864 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700865 return;
866 }
Jason M. Bills16428a12018-11-02 12:42:29 -0700867 const std::string &entryID = params[0];
Jason M. Billse1f26342018-07-18 12:12:00 -0700868 // Convert the unique ID back to a timestamp to find the entry
Jason M. Billse1f26342018-07-18 12:12:00 -0700869 uint64_t ts = 0;
870 uint16_t index = 0;
Jason M. Bills16428a12018-11-02 12:42:29 -0700871 if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
Jason M. Billse1f26342018-07-18 12:12:00 -0700872 {
Jason M. Bills16428a12018-11-02 12:42:29 -0700873 return;
Jason M. Billse1f26342018-07-18 12:12:00 -0700874 }
875
876 sd_journal *journalTmp = nullptr;
877 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
878 if (ret < 0)
879 {
880 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
Jason M. Billsf12894f2018-10-09 12:45:45 -0700881 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700882 return;
883 }
884 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
885 journalTmp, sd_journal_close);
886 journalTmp = nullptr;
887 // Go to the timestamp in the log and move to the entry at the index
888 ret = sd_journal_seek_realtime_usec(journal.get(), ts);
889 for (int i = 0; i <= index; i++)
890 {
891 sd_journal_next(journal.get());
892 }
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800893 // Confirm that the entry ID matches what was requested
894 std::string idStr;
895 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
896 {
897 messages::resourceMissingAtURI(asyncResp->res, entryID);
898 return;
899 }
900
901 if (fillBMCJournalLogEntryJson(entryID, journal.get(),
902 asyncResp->res.jsonValue) != 0)
Jason M. Billse1f26342018-07-18 12:12:00 -0700903 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700904 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700905 return;
906 }
907 }
908};
909
910class CPULogService : public Node
911{
912 public:
913 template <typename CrowApp>
914 CPULogService(CrowApp &app) :
915 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700916 {
Ed Tanous1da66f72018-07-27 16:13:37 -0700917 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700918 {boost::beast::http::verb::get, {{"Login"}}},
919 {boost::beast::http::verb::head, {{"Login"}}},
920 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
921 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
922 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
923 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700924 }
925
926 private:
927 /**
928 * Functions triggers appropriate requests on DBus
929 */
930 void doGet(crow::Response &res, const crow::Request &req,
931 const std::vector<std::string> &params) override
932 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700933 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700934 // Copy over the static data to include the entries added by SubRoute
Ed Tanous0f74e642018-11-12 15:17:05 -0800935 asyncResp->res.jsonValue["@odata.id"] =
936 "/redfish/v1/Managers/bmc/LogServices/CpuLog";
Jason M. Billse1f26342018-07-18 12:12:00 -0700937 asyncResp->res.jsonValue["@odata.type"] =
938 "#LogService.v1_1_0.LogService";
939 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800940 "/redfish/v1/$metadata#LogService.LogService";
Jason M. Billse1f26342018-07-18 12:12:00 -0700941 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service";
942 asyncResp->res.jsonValue["Description"] = "CPU Log Service";
943 asyncResp->res.jsonValue["Id"] = "CPU Log";
944 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
945 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
946 asyncResp->res.jsonValue["Actions"] = {
Ed Tanous1da66f72018-07-27 16:13:37 -0700947 {"Oem",
948 {{"#CpuLog.Immediate",
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800949 {{"target", "/redfish/v1/Managers/bmc/LogServices/CpuLog/"
950 "Actions/Oem/CpuLog.Immediate"}}}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700951
952#ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
Jason M. Billse1f26342018-07-18 12:12:00 -0700953 asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
Ed Tanous1da66f72018-07-27 16:13:37 -0700954 {"#CpuLog.SendRawPeci",
Jason M. Billsc4bf6372018-11-05 13:48:27 -0800955 {{"target", "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/"
956 "Oem/CpuLog.SendRawPeci"}}});
Ed Tanous1da66f72018-07-27 16:13:37 -0700957#endif
Ed Tanous1da66f72018-07-27 16:13:37 -0700958 }
959};
960
Jason M. Billse1f26342018-07-18 12:12:00 -0700961class CPULogEntryCollection : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -0700962{
963 public:
964 template <typename CrowApp>
Jason M. Billse1f26342018-07-18 12:12:00 -0700965 CPULogEntryCollection(CrowApp &app) :
966 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/")
Ed Tanous1da66f72018-07-27 16:13:37 -0700967 {
Ed Tanous1da66f72018-07-27 16:13:37 -0700968 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -0700969 {boost::beast::http::verb::get, {{"Login"}}},
970 {boost::beast::http::verb::head, {{"Login"}}},
971 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
972 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
973 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
974 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -0700975 }
976
977 private:
978 /**
979 * Functions triggers appropriate requests on DBus
980 */
981 void doGet(crow::Response &res, const crow::Request &req,
982 const std::vector<std::string> &params) override
983 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700984 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -0700985 // Collections don't include the static data added by SubRoute because
986 // it has a duplicate entry for members
Jason M. Billse1f26342018-07-18 12:12:00 -0700987 auto getLogEntriesCallback = [asyncResp](
988 const boost::system::error_code ec,
989 const std::vector<std::string> &resp) {
990 if (ec)
991 {
992 if (ec.value() !=
993 boost::system::errc::no_such_file_or_directory)
Ed Tanous1da66f72018-07-27 16:13:37 -0700994 {
Jason M. Billse1f26342018-07-18 12:12:00 -0700995 BMCWEB_LOG_DEBUG << "failed to get entries ec: "
996 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -0700997 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -0700998 return;
Ed Tanous1da66f72018-07-27 16:13:37 -0700999 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001000 }
1001 asyncResp->res.jsonValue["@odata.type"] =
1002 "#LogEntryCollection.LogEntryCollection";
Ed Tanous0f74e642018-11-12 15:17:05 -08001003 asyncResp->res.jsonValue["@odata.id"] =
1004 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
Jason M. Billse1f26342018-07-18 12:12:00 -07001005 asyncResp->res.jsonValue["@odata.context"] =
Jason M. Billsc4bf6372018-11-05 13:48:27 -08001006 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
Jason M. Billse1f26342018-07-18 12:12:00 -07001007 asyncResp->res.jsonValue["@odata.id"] =
1008 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
1009 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries";
1010 asyncResp->res.jsonValue["Description"] =
1011 "Collection of CPU Log Entries";
1012 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1013 logEntryArray = nlohmann::json::array();
1014 for (const std::string &objpath : resp)
1015 {
1016 // Don't list the immediate log
1017 if (objpath.compare(cpuLogImmediatePath) == 0)
Ed Tanous1da66f72018-07-27 16:13:37 -07001018 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001019 continue;
Ed Tanous1da66f72018-07-27 16:13:37 -07001020 }
Jason M. Billse1f26342018-07-18 12:12:00 -07001021 std::size_t lastPos = objpath.rfind("/");
1022 if (lastPos != std::string::npos)
1023 {
1024 logEntryArray.push_back(
1025 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/"
1026 "CpuLog/Entries/" +
1027 objpath.substr(lastPos + 1)}});
1028 }
1029 }
1030 asyncResp->res.jsonValue["Members@odata.count"] =
1031 logEntryArray.size();
1032 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001033 crow::connections::systemBus->async_method_call(
1034 std::move(getLogEntriesCallback),
1035 "xyz.openbmc_project.ObjectMapper",
1036 "/xyz/openbmc_project/object_mapper",
1037 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001038 std::array<const char *, 1>{cpuLogInterface});
Ed Tanous1da66f72018-07-27 16:13:37 -07001039 }
1040};
1041
1042std::string getLogCreatedTime(const nlohmann::json &cpuLog)
1043{
Jason M. Billsc4d00432019-02-12 17:17:48 -08001044 nlohmann::json::const_iterator cdIt = cpuLog.find("crashlog_data");
1045 if (cdIt != cpuLog.end())
Ed Tanous1da66f72018-07-27 16:13:37 -07001046 {
Jason M. Billsc4d00432019-02-12 17:17:48 -08001047 nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO");
1048 if (siIt != cdIt->end())
Ed Tanous1da66f72018-07-27 16:13:37 -07001049 {
Jason M. Billsc4d00432019-02-12 17:17:48 -08001050 nlohmann::json::const_iterator tsIt = siIt->find("timestamp");
1051 if (tsIt != siIt->end())
Ed Tanous1da66f72018-07-27 16:13:37 -07001052 {
Jason M. Billsc4d00432019-02-12 17:17:48 -08001053 const std::string *logTime =
1054 tsIt->get_ptr<const std::string *>();
1055 if (logTime != nullptr)
1056 {
1057 return *logTime;
1058 }
Ed Tanous1da66f72018-07-27 16:13:37 -07001059 }
1060 }
1061 }
1062 BMCWEB_LOG_DEBUG << "failed to find log timestamp";
1063
1064 return std::string();
1065}
1066
Jason M. Billse1f26342018-07-18 12:12:00 -07001067class CPULogEntry : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001068{
1069 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001070 CPULogEntry(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001071 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/",
Ed Tanous1da66f72018-07-27 16:13:37 -07001072 std::string())
1073 {
1074 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001075 {boost::beast::http::verb::get, {{"Login"}}},
1076 {boost::beast::http::verb::head, {{"Login"}}},
1077 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1078 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1079 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1080 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001081 }
1082
1083 private:
1084 void doGet(crow::Response &res, const crow::Request &req,
1085 const std::vector<std::string> &params) override
1086 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001087 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001088 if (params.size() != 1)
1089 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001090 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001091 return;
1092 }
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001093 const uint8_t logId = std::atoi(params[0].c_str());
Ed Tanousabf2add2019-01-22 16:40:12 -08001094 auto getStoredLogCallback = [asyncResp, logId](
1095 const boost::system::error_code ec,
1096 const std::variant<std::string> &resp) {
1097 if (ec)
1098 {
1099 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
1100 messages::internalError(asyncResp->res);
1101 return;
1102 }
1103 const std::string *log = std::get_if<std::string>(&resp);
1104 if (log == nullptr)
1105 {
1106 messages::internalError(asyncResp->res);
1107 return;
1108 }
1109 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1110 if (j.is_discarded())
1111 {
1112 messages::internalError(asyncResp->res);
1113 return;
1114 }
1115 std::string t = getLogCreatedTime(j);
1116 asyncResp->res.jsonValue = {
1117 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
1118 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1119 {"@odata.id",
1120 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" +
1121 std::to_string(logId)},
1122 {"Name", "CPU Debug Log"},
1123 {"Id", logId},
1124 {"EntryType", "Oem"},
1125 {"OemRecordFormat", "Intel CPU Log"},
1126 {"Oem", {{"Intel", std::move(j)}}},
1127 {"Created", std::move(t)}};
1128 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001129 crow::connections::systemBus->async_method_call(
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001130 std::move(getStoredLogCallback), cpuLogObject,
1131 cpuLogPath + std::string("/") + std::to_string(logId),
1132 "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log");
Ed Tanous1da66f72018-07-27 16:13:37 -07001133 }
1134};
1135
Jason M. Billse1f26342018-07-18 12:12:00 -07001136class ImmediateCPULog : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001137{
1138 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001139 ImmediateCPULog(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001140 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
Jason M. Billse1f26342018-07-18 12:12:00 -07001141 "CpuLog.Immediate/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001142 {
1143 entityPrivileges = {
Jason M. Billse1f26342018-07-18 12:12:00 -07001144 {boost::beast::http::verb::get, {{"Login"}}},
1145 {boost::beast::http::verb::head, {{"Login"}}},
1146 {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1147 {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1148 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1149 {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001150 }
1151
1152 private:
1153 void doPost(crow::Response &res, const crow::Request &req,
1154 const std::vector<std::string> &params) override
1155 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001156 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001157 static std::unique_ptr<sdbusplus::bus::match::match>
1158 immediateLogMatcher;
1159
1160 // Only allow one Immediate Log request at a time
1161 if (immediateLogMatcher != nullptr)
1162 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001163 asyncResp->res.addHeader("Retry-After", "30");
Jason M. Billsf12894f2018-10-09 12:45:45 -07001164 messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
Ed Tanous1da66f72018-07-27 16:13:37 -07001165 return;
1166 }
1167 // Make this static so it survives outside this method
1168 static boost::asio::deadline_timer timeout(*req.ioService);
1169
1170 timeout.expires_from_now(boost::posix_time::seconds(30));
Jason M. Billse1f26342018-07-18 12:12:00 -07001171 timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
Ed Tanous1da66f72018-07-27 16:13:37 -07001172 immediateLogMatcher = nullptr;
1173 if (ec)
1174 {
1175 // operation_aborted is expected if timer is canceled before
1176 // completion.
1177 if (ec != boost::asio::error::operation_aborted)
1178 {
1179 BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
1180 }
1181 return;
1182 }
1183 BMCWEB_LOG_ERROR << "Timed out waiting for immediate log";
1184
Jason M. Billsf12894f2018-10-09 12:45:45 -07001185 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001186 });
1187
Jason M. Billse1f26342018-07-18 12:12:00 -07001188 auto immediateLogMatcherCallback = [asyncResp](
Ed Tanous1da66f72018-07-27 16:13:37 -07001189 sdbusplus::message::message &m) {
1190 BMCWEB_LOG_DEBUG << "Immediate log available match fired";
1191 boost::system::error_code ec;
1192 timeout.cancel(ec);
1193 if (ec)
1194 {
1195 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
1196 }
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001197 sdbusplus::message::object_path objPath;
Ed Tanous1da66f72018-07-27 16:13:37 -07001198 boost::container::flat_map<
Ed Tanousabf2add2019-01-22 16:40:12 -08001199 std::string, boost::container::flat_map<
1200 std::string, std::variant<std::string>>>
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001201 interfacesAdded;
1202 m.read(objPath, interfacesAdded);
Ed Tanousabf2add2019-01-22 16:40:12 -08001203 const std::string *log = std::get_if<std::string>(
1204 &interfacesAdded[cpuLogInterface]["Log"]);
Ed Tanous1da66f72018-07-27 16:13:37 -07001205 if (log == nullptr)
1206 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001207 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001208 // Careful with immediateLogMatcher. It is a unique_ptr to the
1209 // match object inside which this lambda is executing. Once it
1210 // is set to nullptr, the match object will be destroyed and the
1211 // lambda will lose its context, including res, so it needs to
1212 // be the last thing done.
1213 immediateLogMatcher = nullptr;
1214 return;
1215 }
1216 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1217 if (j.is_discarded())
1218 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001219 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001220 // Careful with immediateLogMatcher. It is a unique_ptr to the
1221 // match object inside which this lambda is executing. Once it
1222 // is set to nullptr, the match object will be destroyed and the
1223 // lambda will lose its context, including res, so it needs to
1224 // be the last thing done.
1225 immediateLogMatcher = nullptr;
1226 return;
1227 }
1228 std::string t = getLogCreatedTime(j);
Jason M. Billse1f26342018-07-18 12:12:00 -07001229 asyncResp->res.jsonValue = {
Ed Tanous1da66f72018-07-27 16:13:37 -07001230 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
1231 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1232 {"Name", "CPU Debug Log"},
1233 {"EntryType", "Oem"},
1234 {"OemRecordFormat", "Intel CPU Log"},
1235 {"Oem", {{"Intel", std::move(j)}}},
1236 {"Created", std::move(t)}};
Ed Tanous1da66f72018-07-27 16:13:37 -07001237 // Careful with immediateLogMatcher. It is a unique_ptr to the
1238 // match object inside which this lambda is executing. Once it is
1239 // set to nullptr, the match object will be destroyed and the lambda
1240 // will lose its context, including res, so it needs to be the last
1241 // thing done.
1242 immediateLogMatcher = nullptr;
1243 };
1244 immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
1245 *crow::connections::systemBus,
1246 sdbusplus::bus::match::rules::interfacesAdded() +
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001247 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath),
Ed Tanous1da66f72018-07-27 16:13:37 -07001248 std::move(immediateLogMatcherCallback));
1249
1250 auto generateImmediateLogCallback =
Jason M. Billse1f26342018-07-18 12:12:00 -07001251 [asyncResp](const boost::system::error_code ec,
1252 const std::string &resp) {
Ed Tanous1da66f72018-07-27 16:13:37 -07001253 if (ec)
1254 {
1255 if (ec.value() ==
1256 boost::system::errc::operation_not_supported)
1257 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001258 messages::resourceInStandby(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001259 }
1260 else
1261 {
Jason M. Billsf12894f2018-10-09 12:45:45 -07001262 messages::internalError(asyncResp->res);
Ed Tanous1da66f72018-07-27 16:13:37 -07001263 }
Ed Tanous1da66f72018-07-27 16:13:37 -07001264 boost::system::error_code timeoutec;
1265 timeout.cancel(timeoutec);
1266 if (timeoutec)
1267 {
1268 BMCWEB_LOG_ERROR << "error canceling timer "
1269 << timeoutec;
1270 }
1271 immediateLogMatcher = nullptr;
1272 return;
1273 }
1274 };
1275 crow::connections::systemBus->async_method_call(
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001276 std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath,
1277 cpuLogImmediateInterface, "GenerateImmediateLog");
Ed Tanous1da66f72018-07-27 16:13:37 -07001278 }
1279};
1280
Jason M. Billse1f26342018-07-18 12:12:00 -07001281class SendRawPECI : public Node
Ed Tanous1da66f72018-07-27 16:13:37 -07001282{
1283 public:
Jason M. Billse1f26342018-07-18 12:12:00 -07001284 SendRawPECI(CrowApp &app) :
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001285 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
Jason M. Billse1f26342018-07-18 12:12:00 -07001286 "CpuLog.SendRawPeci/")
Ed Tanous1da66f72018-07-27 16:13:37 -07001287 {
1288 entityPrivileges = {
1289 {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
1290 {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
1291 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1292 {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1293 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1294 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1295 }
1296
1297 private:
1298 void doPost(crow::Response &res, const crow::Request &req,
1299 const std::vector<std::string> &params) override
1300 {
Jason M. Billse1f26342018-07-18 12:12:00 -07001301 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanousb1556422018-10-16 14:09:17 -07001302 uint8_t clientAddress = 0;
1303 uint8_t readLength = 0;
Ed Tanous1da66f72018-07-27 16:13:37 -07001304 std::vector<uint8_t> peciCommand;
Ed Tanousb1556422018-10-16 14:09:17 -07001305 if (!json_util::readJson(req, res, "ClientAddress", clientAddress,
1306 "ReadLength", readLength, "PECICommand",
1307 peciCommand))
Ed Tanous1da66f72018-07-27 16:13:37 -07001308 {
Ed Tanousb1556422018-10-16 14:09:17 -07001309 return;
Ed Tanous1da66f72018-07-27 16:13:37 -07001310 }
Ed Tanousb1556422018-10-16 14:09:17 -07001311
Ed Tanous1da66f72018-07-27 16:13:37 -07001312 // Callback to return the Raw PECI response
Jason M. Billse1f26342018-07-18 12:12:00 -07001313 auto sendRawPECICallback =
1314 [asyncResp](const boost::system::error_code ec,
1315 const std::vector<uint8_t> &resp) {
1316 if (ec)
1317 {
1318 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
1319 << ec.message();
Jason M. Billsf12894f2018-10-09 12:45:45 -07001320 messages::internalError(asyncResp->res);
Jason M. Billse1f26342018-07-18 12:12:00 -07001321 return;
1322 }
1323 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
1324 {"PECIResponse", resp}};
1325 };
Ed Tanous1da66f72018-07-27 16:13:37 -07001326 // Call the SendRawPECI command with the provided data
1327 crow::connections::systemBus->async_method_call(
Jason M. Billse1f26342018-07-18 12:12:00 -07001328 std::move(sendRawPECICallback), cpuLogObject, cpuLogPath,
1329 cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength,
Ed Tanous4ed77cd2018-10-15 08:08:07 -07001330 peciCommand);
Ed Tanous1da66f72018-07-27 16:13:37 -07001331 }
1332};
1333
1334} // namespace redfish