1 /*
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 "app.hpp"
19 #include "dbus_utility.hpp"
20 #include "error_messages.hpp"
21 #include "generated/enums/log_entry.hpp"
22 #include "gzfile.hpp"
23 #include "http_utility.hpp"
24 #include "human_sort.hpp"
25 #include "query.hpp"
26 #include "registries.hpp"
27 #include "registries/base_message_registry.hpp"
28 #include "registries/openbmc_message_registry.hpp"
29 #include "registries/privilege_registry.hpp"
30 #include "task.hpp"
31 #include "task_messages.hpp"
32 #include "utils/dbus_utils.hpp"
33 #include "utils/json_utils.hpp"
34 #include "utils/time_utils.hpp"
35
36 #include <systemd/sd-id128.h>
37 #include <systemd/sd-journal.h>
38 #include <tinyxml2.h>
39 #include <unistd.h>
40
41 #include <boost/beast/http/verb.hpp>
42 #include <boost/container/flat_map.hpp>
43 #include <boost/system/linux_error.hpp>
44 #include <boost/url/format.hpp>
45 #include <sdbusplus/asio/property.hpp>
46 #include <sdbusplus/unpack_properties.hpp>
47
48 #include <array>
49 #include <charconv>
50 #include <cstddef>
51 #include <filesystem>
52 #include <iterator>
53 #include <optional>
54 #include <ranges>
55 #include <span>
56 #include <string>
57 #include <string_view>
58 #include <variant>
59
60 namespace redfish
61 {
62
63 constexpr const char* crashdumpObject = "com.intel.crashdump";
64 constexpr const char* crashdumpPath = "/com/intel/crashdump";
65 constexpr const char* crashdumpInterface = "com.intel.crashdump";
66 constexpr const char* deleteAllInterface =
67 "xyz.openbmc_project.Collection.DeleteAll";
68 constexpr const char* crashdumpOnDemandInterface =
69 "com.intel.crashdump.OnDemand";
70 constexpr const char* crashdumpTelemetryInterface =
71 "com.intel.crashdump.Telemetry";
72
73 enum class DumpCreationProgress
74 {
75 DUMP_CREATE_SUCCESS,
76 DUMP_CREATE_FAILED,
77 DUMP_CREATE_INPROGRESS
78 };
79
80 namespace fs = std::filesystem;
81
translateSeverityDbusToRedfish(const std::string & s)82 inline std::string translateSeverityDbusToRedfish(const std::string& s)
83 {
84 if ((s == "xyz.openbmc_project.Logging.Entry.Level.Alert") ||
85 (s == "xyz.openbmc_project.Logging.Entry.Level.Critical") ||
86 (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") ||
87 (s == "xyz.openbmc_project.Logging.Entry.Level.Error"))
88 {
89 return "Critical";
90 }
91 if ((s == "xyz.openbmc_project.Logging.Entry.Level.Debug") ||
92 (s == "xyz.openbmc_project.Logging.Entry.Level.Informational") ||
93 (s == "xyz.openbmc_project.Logging.Entry.Level.Notice"))
94 {
95 return "OK";
96 }
97 if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
98 {
99 return "Warning";
100 }
101 return "";
102 }
103
getProviderNotifyAction(const std::string & notify)104 inline std::optional<bool> getProviderNotifyAction(const std::string& notify)
105 {
106 std::optional<bool> notifyAction;
107 if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Notify")
108 {
109 notifyAction = true;
110 }
111 else if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Inhibit")
112 {
113 notifyAction = false;
114 }
115
116 return notifyAction;
117 }
118
getDumpPath(std::string_view dumpType)119 inline std::string getDumpPath(std::string_view dumpType)
120 {
121 std::string dbusDumpPath = "/xyz/openbmc_project/dump/";
122 std::ranges::transform(dumpType, std::back_inserter(dbusDumpPath),
123 bmcweb::asciiToLower);
124
125 return dbusDumpPath;
126 }
127
getJournalMetadata(sd_journal * journal,std::string_view field,std::string_view & contents)128 inline int getJournalMetadata(sd_journal* journal, std::string_view field,
129 std::string_view& contents)
130 {
131 const char* data = nullptr;
132 size_t length = 0;
133 int ret = 0;
134 // Get the metadata from the requested field of the journal entry
135 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
136 const void** dataVoid = reinterpret_cast<const void**>(&data);
137
138 ret = sd_journal_get_data(journal, field.data(), dataVoid, &length);
139 if (ret < 0)
140 {
141 return ret;
142 }
143 contents = std::string_view(data, length);
144 // Only use the content after the "=" character.
145 contents.remove_prefix(std::min(contents.find('=') + 1, contents.size()));
146 return ret;
147 }
148
getJournalMetadata(sd_journal * journal,std::string_view field,const int & base,long int & contents)149 inline int getJournalMetadata(sd_journal* journal, std::string_view field,
150 const int& base, long int& contents)
151 {
152 int ret = 0;
153 std::string_view metadata;
154 // Get the metadata from the requested field of the journal entry
155 ret = getJournalMetadata(journal, field, metadata);
156 if (ret < 0)
157 {
158 return ret;
159 }
160 contents = strtol(metadata.data(), nullptr, base);
161 return ret;
162 }
163
getEntryTimestamp(sd_journal * journal,std::string & entryTimestamp)164 inline bool getEntryTimestamp(sd_journal* journal, std::string& entryTimestamp)
165 {
166 int ret = 0;
167 uint64_t timestamp = 0;
168 ret = sd_journal_get_realtime_usec(journal, ×tamp);
169 if (ret < 0)
170 {
171 BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}", strerror(-ret));
172 return false;
173 }
174 entryTimestamp = redfish::time_utils::getDateTimeUintUs(timestamp);
175 return true;
176 }
177
getUniqueEntryID(sd_journal * journal,std::string & entryID,const bool firstEntry=true)178 inline bool getUniqueEntryID(sd_journal* journal, std::string& entryID,
179 const bool firstEntry = true)
180 {
181 int ret = 0;
182 static sd_id128_t prevBootID{};
183 static uint64_t prevTs = 0;
184 static int index = 0;
185 if (firstEntry)
186 {
187 prevBootID = {};
188 prevTs = 0;
189 }
190
191 // Get the entry timestamp
192 uint64_t curTs = 0;
193 sd_id128_t curBootID{};
194 ret = sd_journal_get_monotonic_usec(journal, &curTs, &curBootID);
195 if (ret < 0)
196 {
197 BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}", strerror(-ret));
198 return false;
199 }
200 // If the timestamp isn't unique on the same boot, increment the index
201 bool sameBootIDs = sd_id128_equal(curBootID, prevBootID) != 0;
202 if (sameBootIDs && (curTs == prevTs))
203 {
204 index++;
205 }
206 else
207 {
208 // Otherwise, reset it
209 index = 0;
210 }
211
212 if (!sameBootIDs)
213 {
214 // Save the bootID
215 prevBootID = curBootID;
216 }
217 // Save the timestamp
218 prevTs = curTs;
219
220 // make entryID as <bootID>_<timestamp>[_<index>]
221 std::array<char, SD_ID128_STRING_MAX> bootIDStr{};
222 sd_id128_to_string(curBootID, bootIDStr.data());
223 entryID = std::format("{}_{}", bootIDStr.data(), curTs);
224 if (index > 0)
225 {
226 entryID += "_" + std::to_string(index);
227 }
228 return true;
229 }
230
getUniqueEntryID(const std::string & logEntry,std::string & entryID,const bool firstEntry=true)231 static bool getUniqueEntryID(const std::string& logEntry, std::string& entryID,
232 const bool firstEntry = true)
233 {
234 static time_t prevTs = 0;
235 static int index = 0;
236 if (firstEntry)
237 {
238 prevTs = 0;
239 }
240
241 // Get the entry timestamp
242 std::time_t curTs = 0;
243 std::tm timeStruct = {};
244 std::istringstream entryStream(logEntry);
245 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
246 {
247 curTs = std::mktime(&timeStruct);
248 }
249 // If the timestamp isn't unique, increment the index
250 if (curTs == prevTs)
251 {
252 index++;
253 }
254 else
255 {
256 // Otherwise, reset it
257 index = 0;
258 }
259 // Save the timestamp
260 prevTs = curTs;
261
262 entryID = std::to_string(curTs);
263 if (index > 0)
264 {
265 entryID += "_" + std::to_string(index);
266 }
267 return true;
268 }
269
270 // Entry is formed like "BootID_timestamp" or "BootID_timestamp_index"
271 inline bool
getTimestampFromID(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string_view entryIDStrView,sd_id128_t & bootID,uint64_t & timestamp,uint64_t & index)272 getTimestampFromID(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
273 std::string_view entryIDStrView, sd_id128_t& bootID,
274 uint64_t& timestamp, uint64_t& index)
275 {
276 // Convert the unique ID back to a bootID + timestamp to find the entry
277 auto underscore1Pos = entryIDStrView.find('_');
278 if (underscore1Pos == std::string_view::npos)
279 {
280 // EntryID has no bootID or timestamp
281 messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView);
282 return false;
283 }
284
285 // EntryID has bootID + timestamp
286
287 // Convert entryIDViewString to BootID
288 // NOTE: bootID string which needs to be null-terminated for
289 // sd_id128_from_string()
290 std::string bootIDStr(entryIDStrView.substr(0, underscore1Pos));
291 if (sd_id128_from_string(bootIDStr.c_str(), &bootID) < 0)
292 {
293 messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView);
294 return false;
295 }
296
297 // Get the timestamp from entryID
298 entryIDStrView.remove_prefix(underscore1Pos + 1);
299
300 auto [timestampEnd, tstampEc] = std::from_chars(
301 entryIDStrView.begin(), entryIDStrView.end(), timestamp);
302 if (tstampEc != std::errc())
303 {
304 messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView);
305 return false;
306 }
307 entryIDStrView = std::string_view(
308 timestampEnd,
309 static_cast<size_t>(std::distance(timestampEnd, entryIDStrView.end())));
310 if (entryIDStrView.empty())
311 {
312 index = 0U;
313 return true;
314 }
315 // Timestamp might include optional index, if two events happened at the
316 // same "time".
317 if (entryIDStrView[0] != '_')
318 {
319 messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView);
320 return false;
321 }
322 entryIDStrView.remove_prefix(1);
323 auto [ptr, indexEc] = std::from_chars(entryIDStrView.begin(),
324 entryIDStrView.end(), index);
325 if (indexEc != std::errc() || ptr != entryIDStrView.end())
326 {
327 messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView);
328 return false;
329 }
330 return true;
331 }
332
333 static bool
getRedfishLogFiles(std::vector<std::filesystem::path> & redfishLogFiles)334 getRedfishLogFiles(std::vector<std::filesystem::path>& redfishLogFiles)
335 {
336 static const std::filesystem::path redfishLogDir = "/var/log";
337 static const std::string redfishLogFilename = "redfish";
338
339 // Loop through the directory looking for redfish log files
340 for (const std::filesystem::directory_entry& dirEnt :
341 std::filesystem::directory_iterator(redfishLogDir))
342 {
343 // If we find a redfish log file, save the path
344 std::string filename = dirEnt.path().filename();
345 if (filename.starts_with(redfishLogFilename))
346 {
347 redfishLogFiles.emplace_back(redfishLogDir / filename);
348 }
349 }
350 // As the log files rotate, they are appended with a ".#" that is higher for
351 // the older logs. Since we don't expect more than 10 log files, we
352 // can just sort the list to get them in order from newest to oldest
353 std::ranges::sort(redfishLogFiles);
354
355 return !redfishLogFiles.empty();
356 }
357
358 inline log_entry::OriginatorTypes
mapDbusOriginatorTypeToRedfish(const std::string & originatorType)359 mapDbusOriginatorTypeToRedfish(const std::string& originatorType)
360 {
361 if (originatorType ==
362 "xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.Client")
363 {
364 return log_entry::OriginatorTypes::Client;
365 }
366 if (originatorType ==
367 "xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.Internal")
368 {
369 return log_entry::OriginatorTypes::Internal;
370 }
371 if (originatorType ==
372 "xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.SupportingService")
373 {
374 return log_entry::OriginatorTypes::SupportingService;
375 }
376 return log_entry::OriginatorTypes::Invalid;
377 }
378
parseDumpEntryFromDbusObject(const dbus::utility::ManagedObjectType::value_type & object,std::string & dumpStatus,uint64_t & size,uint64_t & timestampUs,std::string & originatorId,log_entry::OriginatorTypes & originatorType,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)379 inline void parseDumpEntryFromDbusObject(
380 const dbus::utility::ManagedObjectType::value_type& object,
381 std::string& dumpStatus, uint64_t& size, uint64_t& timestampUs,
382 std::string& originatorId, log_entry::OriginatorTypes& originatorType,
383 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
384 {
385 for (const auto& interfaceMap : object.second)
386 {
387 if (interfaceMap.first == "xyz.openbmc_project.Common.Progress")
388 {
389 for (const auto& propertyMap : interfaceMap.second)
390 {
391 if (propertyMap.first == "Status")
392 {
393 const auto* status =
394 std::get_if<std::string>(&propertyMap.second);
395 if (status == nullptr)
396 {
397 messages::internalError(asyncResp->res);
398 break;
399 }
400 dumpStatus = *status;
401 }
402 }
403 }
404 else if (interfaceMap.first == "xyz.openbmc_project.Dump.Entry")
405 {
406 for (const auto& propertyMap : interfaceMap.second)
407 {
408 if (propertyMap.first == "Size")
409 {
410 const auto* sizePtr =
411 std::get_if<uint64_t>(&propertyMap.second);
412 if (sizePtr == nullptr)
413 {
414 messages::internalError(asyncResp->res);
415 break;
416 }
417 size = *sizePtr;
418 break;
419 }
420 }
421 }
422 else if (interfaceMap.first == "xyz.openbmc_project.Time.EpochTime")
423 {
424 for (const auto& propertyMap : interfaceMap.second)
425 {
426 if (propertyMap.first == "Elapsed")
427 {
428 const uint64_t* usecsTimeStamp =
429 std::get_if<uint64_t>(&propertyMap.second);
430 if (usecsTimeStamp == nullptr)
431 {
432 messages::internalError(asyncResp->res);
433 break;
434 }
435 timestampUs = *usecsTimeStamp;
436 break;
437 }
438 }
439 }
440 else if (interfaceMap.first ==
441 "xyz.openbmc_project.Common.OriginatedBy")
442 {
443 for (const auto& propertyMap : interfaceMap.second)
444 {
445 if (propertyMap.first == "OriginatorId")
446 {
447 const std::string* id =
448 std::get_if<std::string>(&propertyMap.second);
449 if (id == nullptr)
450 {
451 messages::internalError(asyncResp->res);
452 break;
453 }
454 originatorId = *id;
455 }
456
457 if (propertyMap.first == "OriginatorType")
458 {
459 const std::string* type =
460 std::get_if<std::string>(&propertyMap.second);
461 if (type == nullptr)
462 {
463 messages::internalError(asyncResp->res);
464 break;
465 }
466
467 originatorType = mapDbusOriginatorTypeToRedfish(*type);
468 if (originatorType == log_entry::OriginatorTypes::Invalid)
469 {
470 messages::internalError(asyncResp->res);
471 break;
472 }
473 }
474 }
475 }
476 }
477 }
478
getDumpEntriesPath(const std::string & dumpType)479 static std::string getDumpEntriesPath(const std::string& dumpType)
480 {
481 std::string entriesPath;
482
483 if (dumpType == "BMC")
484 {
485 entriesPath =
486 std::format("/redfish/v1/Managers/{}/LogServices/Dump/Entries/",
487 BMCWEB_REDFISH_MANAGER_URI_NAME);
488 }
489 else if (dumpType == "FaultLog")
490 {
491 entriesPath =
492 std::format("/redfish/v1/Managers/{}/LogServices/FaultLog/Entries/",
493 BMCWEB_REDFISH_MANAGER_URI_NAME);
494 }
495 else if (dumpType == "System")
496 {
497 entriesPath =
498 std::format("/redfish/v1/Systems/{}/LogServices/Dump/Entries/",
499 BMCWEB_REDFISH_SYSTEM_URI_NAME);
500 }
501 else
502 {
503 BMCWEB_LOG_ERROR("getDumpEntriesPath() invalid dump type: {}",
504 dumpType);
505 }
506
507 // Returns empty string on error
508 return entriesPath;
509 }
510
511 inline void
getDumpEntryCollection(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & dumpType)512 getDumpEntryCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
513 const std::string& dumpType)
514 {
515 std::string entriesPath = getDumpEntriesPath(dumpType);
516 if (entriesPath.empty())
517 {
518 messages::internalError(asyncResp->res);
519 return;
520 }
521
522 sdbusplus::message::object_path path("/xyz/openbmc_project/dump");
523 dbus::utility::getManagedObjects(
524 "xyz.openbmc_project.Dump.Manager", path,
525 [asyncResp, entriesPath,
526 dumpType](const boost::system::error_code& ec,
527 const dbus::utility::ManagedObjectType& objects) {
528 if (ec)
529 {
530 BMCWEB_LOG_ERROR("DumpEntry resp_handler got error {}", ec);
531 messages::internalError(asyncResp->res);
532 return;
533 }
534
535 // Remove ending slash
536 std::string odataIdStr = entriesPath;
537 if (!odataIdStr.empty())
538 {
539 odataIdStr.pop_back();
540 }
541
542 asyncResp->res.jsonValue["@odata.type"] =
543 "#LogEntryCollection.LogEntryCollection";
544 asyncResp->res.jsonValue["@odata.id"] = std::move(odataIdStr);
545 asyncResp->res.jsonValue["Name"] = dumpType + " Dump Entries";
546 asyncResp->res.jsonValue["Description"] = "Collection of " + dumpType +
547 " Dump Entries";
548
549 nlohmann::json::array_t entriesArray;
550 std::string dumpEntryPath = getDumpPath(dumpType) + "/entry/";
551
552 dbus::utility::ManagedObjectType resp(objects);
553 std::ranges::sort(resp, [](const auto& l, const auto& r) {
554 return AlphanumLess<std::string>()(l.first.filename(),
555 r.first.filename());
556 });
557
558 for (auto& object : resp)
559 {
560 if (object.first.str.find(dumpEntryPath) == std::string::npos)
561 {
562 continue;
563 }
564 uint64_t timestampUs = 0;
565 uint64_t size = 0;
566 std::string dumpStatus;
567 std::string originatorId;
568 log_entry::OriginatorTypes originatorType =
569 log_entry::OriginatorTypes::Internal;
570 nlohmann::json::object_t thisEntry;
571
572 std::string entryID = object.first.filename();
573 if (entryID.empty())
574 {
575 continue;
576 }
577
578 parseDumpEntryFromDbusObject(object, dumpStatus, size, timestampUs,
579 originatorId, originatorType,
580 asyncResp);
581
582 if (dumpStatus !=
583 "xyz.openbmc_project.Common.Progress.OperationStatus.Completed" &&
584 !dumpStatus.empty())
585 {
586 // Dump status is not Complete, no need to enumerate
587 continue;
588 }
589
590 thisEntry["@odata.type"] = "#LogEntry.v1_11_0.LogEntry";
591 thisEntry["@odata.id"] = entriesPath + entryID;
592 thisEntry["Id"] = entryID;
593 thisEntry["EntryType"] = "Event";
594 thisEntry["Name"] = dumpType + " Dump Entry";
595 thisEntry["Created"] =
596 redfish::time_utils::getDateTimeUintUs(timestampUs);
597
598 if (!originatorId.empty())
599 {
600 thisEntry["Originator"] = originatorId;
601 thisEntry["OriginatorType"] = originatorType;
602 }
603
604 if (dumpType == "BMC")
605 {
606 thisEntry["DiagnosticDataType"] = "Manager";
607 thisEntry["AdditionalDataURI"] = entriesPath + entryID +
608 "/attachment";
609 thisEntry["AdditionalDataSizeBytes"] = size;
610 }
611 else if (dumpType == "System")
612 {
613 thisEntry["DiagnosticDataType"] = "OEM";
614 thisEntry["OEMDiagnosticDataType"] = "System";
615 thisEntry["AdditionalDataURI"] = entriesPath + entryID +
616 "/attachment";
617 thisEntry["AdditionalDataSizeBytes"] = size;
618 }
619 entriesArray.emplace_back(std::move(thisEntry));
620 }
621 asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size();
622 asyncResp->res.jsonValue["Members"] = std::move(entriesArray);
623 });
624 }
625
626 inline void
getDumpEntryById(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & entryID,const std::string & dumpType)627 getDumpEntryById(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
628 const std::string& entryID, const std::string& dumpType)
629 {
630 std::string entriesPath = getDumpEntriesPath(dumpType);
631 if (entriesPath.empty())
632 {
633 messages::internalError(asyncResp->res);
634 return;
635 }
636
637 sdbusplus::message::object_path path("/xyz/openbmc_project/dump");
638 dbus::utility::getManagedObjects(
639 "xyz.openbmc_project.Dump.Manager", path,
640 [asyncResp, entryID, dumpType,
641 entriesPath](const boost::system::error_code& ec,
642 const dbus::utility::ManagedObjectType& resp) {
643 if (ec)
644 {
645 BMCWEB_LOG_ERROR("DumpEntry resp_handler got error {}", ec);
646 messages::internalError(asyncResp->res);
647 return;
648 }
649
650 bool foundDumpEntry = false;
651 std::string dumpEntryPath = getDumpPath(dumpType) + "/entry/";
652
653 for (const auto& objectPath : resp)
654 {
655 if (objectPath.first.str != dumpEntryPath + entryID)
656 {
657 continue;
658 }
659
660 foundDumpEntry = true;
661 uint64_t timestampUs = 0;
662 uint64_t size = 0;
663 std::string dumpStatus;
664 std::string originatorId;
665 log_entry::OriginatorTypes originatorType =
666 log_entry::OriginatorTypes::Internal;
667
668 parseDumpEntryFromDbusObject(objectPath, dumpStatus, size,
669 timestampUs, originatorId,
670 originatorType, asyncResp);
671
672 if (dumpStatus !=
673 "xyz.openbmc_project.Common.Progress.OperationStatus.Completed" &&
674 !dumpStatus.empty())
675 {
676 // Dump status is not Complete
677 // return not found until status is changed to Completed
678 messages::resourceNotFound(asyncResp->res, dumpType + " dump",
679 entryID);
680 return;
681 }
682
683 asyncResp->res.jsonValue["@odata.type"] =
684 "#LogEntry.v1_11_0.LogEntry";
685 asyncResp->res.jsonValue["@odata.id"] = entriesPath + entryID;
686 asyncResp->res.jsonValue["Id"] = entryID;
687 asyncResp->res.jsonValue["EntryType"] = "Event";
688 asyncResp->res.jsonValue["Name"] = dumpType + " Dump Entry";
689 asyncResp->res.jsonValue["Created"] =
690 redfish::time_utils::getDateTimeUintUs(timestampUs);
691
692 if (!originatorId.empty())
693 {
694 asyncResp->res.jsonValue["Originator"] = originatorId;
695 asyncResp->res.jsonValue["OriginatorType"] = originatorType;
696 }
697
698 if (dumpType == "BMC")
699 {
700 asyncResp->res.jsonValue["DiagnosticDataType"] = "Manager";
701 asyncResp->res.jsonValue["AdditionalDataURI"] =
702 entriesPath + entryID + "/attachment";
703 asyncResp->res.jsonValue["AdditionalDataSizeBytes"] = size;
704 }
705 else if (dumpType == "System")
706 {
707 asyncResp->res.jsonValue["DiagnosticDataType"] = "OEM";
708 asyncResp->res.jsonValue["OEMDiagnosticDataType"] = "System";
709 asyncResp->res.jsonValue["AdditionalDataURI"] =
710 entriesPath + entryID + "/attachment";
711 asyncResp->res.jsonValue["AdditionalDataSizeBytes"] = size;
712 }
713 }
714 if (!foundDumpEntry)
715 {
716 BMCWEB_LOG_WARNING("Can't find Dump Entry {}", entryID);
717 messages::resourceNotFound(asyncResp->res, dumpType + " dump",
718 entryID);
719 return;
720 }
721 });
722 }
723
deleteDumpEntry(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & entryID,const std::string & dumpType)724 inline void deleteDumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
725 const std::string& entryID,
726 const std::string& dumpType)
727 {
728 auto respHandler = [asyncResp,
729 entryID](const boost::system::error_code& ec) {
730 BMCWEB_LOG_DEBUG("Dump Entry doDelete callback: Done");
731 if (ec)
732 {
733 if (ec.value() == EBADR)
734 {
735 messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
736 return;
737 }
738 BMCWEB_LOG_ERROR(
739 "Dump (DBus) doDelete respHandler got error {} entryID={}", ec,
740 entryID);
741 messages::internalError(asyncResp->res);
742 return;
743 }
744 };
745
746 crow::connections::systemBus->async_method_call(
747 respHandler, "xyz.openbmc_project.Dump.Manager",
748 std::format("{}/entry/{}", getDumpPath(dumpType), entryID),
749 "xyz.openbmc_project.Object.Delete", "Delete");
750 }
checkSizeLimit(int fd,crow::Response & res)751 inline bool checkSizeLimit(int fd, crow::Response& res)
752 {
753 long long int size = lseek(fd, 0, SEEK_END);
754 if (size <= 0)
755 {
756 BMCWEB_LOG_ERROR("Failed to get size of file, lseek() returned {}",
757 size);
758 messages::internalError(res);
759 return false;
760 }
761
762 // Arbitrary max size of 20MB to accommodate BMC dumps
763 constexpr long long int maxFileSize = 20LL * 1024LL * 1024LL;
764 if (size > maxFileSize)
765 {
766 BMCWEB_LOG_ERROR("File size {} exceeds maximum allowed size of {}",
767 size, maxFileSize);
768 messages::internalError(res);
769 return false;
770 }
771 off_t rc = lseek(fd, 0, SEEK_SET);
772 if (rc < 0)
773 {
774 BMCWEB_LOG_ERROR("Failed to reset file offset to 0");
775 messages::internalError(res);
776 return false;
777 }
778 return true;
779 }
780 inline void
downloadEntryCallback(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & entryID,const std::string & downloadEntryType,const boost::system::error_code & ec,const sdbusplus::message::unix_fd & unixfd)781 downloadEntryCallback(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
782 const std::string& entryID,
783 const std::string& downloadEntryType,
784 const boost::system::error_code& ec,
785 const sdbusplus::message::unix_fd& unixfd)
786 {
787 if (ec.value() == EBADR)
788 {
789 messages::resourceNotFound(asyncResp->res, "EntryAttachment", entryID);
790 return;
791 }
792 if (ec)
793 {
794 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
795 messages::internalError(asyncResp->res);
796 return;
797 }
798
799 // Make sure we know how to process the retrieved entry attachment
800 if ((downloadEntryType != "BMC") && (downloadEntryType != "System"))
801 {
802 BMCWEB_LOG_ERROR("downloadEntryCallback() invalid entry type: {}",
803 downloadEntryType);
804 messages::internalError(asyncResp->res);
805 }
806
807 int fd = -1;
808 fd = dup(unixfd);
809 if (fd < 0)
810 {
811 BMCWEB_LOG_ERROR("Failed to open file");
812 messages::internalError(asyncResp->res);
813 return;
814 }
815 if (!checkSizeLimit(fd, asyncResp->res))
816 {
817 close(fd);
818 return;
819 }
820 if (downloadEntryType == "System")
821 {
822 if (!asyncResp->res.openFd(fd, bmcweb::EncodingType::Base64))
823 {
824 messages::internalError(asyncResp->res);
825 close(fd);
826 return;
827 }
828 asyncResp->res.addHeader(
829 boost::beast::http::field::content_transfer_encoding, "Base64");
830 return;
831 }
832 if (!asyncResp->res.openFd(fd))
833 {
834 messages::internalError(asyncResp->res);
835 close(fd);
836 return;
837 }
838 asyncResp->res.addHeader(boost::beast::http::field::content_type,
839 "application/octet-stream");
840 }
841
842 inline void
downloadDumpEntry(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & entryID,const std::string & dumpType)843 downloadDumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
844 const std::string& entryID, const std::string& dumpType)
845 {
846 if (dumpType != "BMC")
847 {
848 BMCWEB_LOG_WARNING("Can't find Dump Entry {}", entryID);
849 messages::resourceNotFound(asyncResp->res, dumpType + " dump", entryID);
850 return;
851 }
852
853 std::string dumpEntryPath = std::format("{}/entry/{}",
854 getDumpPath(dumpType), entryID);
855
856 auto downloadDumpEntryHandler =
857 [asyncResp, entryID,
858 dumpType](const boost::system::error_code& ec,
859 const sdbusplus::message::unix_fd& unixfd) {
860 downloadEntryCallback(asyncResp, entryID, dumpType, ec, unixfd);
861 };
862
863 crow::connections::systemBus->async_method_call(
864 std::move(downloadDumpEntryHandler), "xyz.openbmc_project.Dump.Manager",
865 dumpEntryPath, "xyz.openbmc_project.Dump.Entry", "GetFileHandle");
866 }
867
868 inline void
downloadEventLogEntry(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName,const std::string & entryID,const std::string & dumpType)869 downloadEventLogEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
870 const std::string& systemName,
871 const std::string& entryID,
872 const std::string& dumpType)
873 {
874 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
875 {
876 // Option currently returns no systems. TBD
877 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
878 systemName);
879 return;
880 }
881 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
882 {
883 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
884 systemName);
885 return;
886 }
887
888 std::string entryPath =
889 sdbusplus::message::object_path("/xyz/openbmc_project/logging/entry") /
890 entryID;
891
892 auto downloadEventLogEntryHandler =
893 [asyncResp, entryID,
894 dumpType](const boost::system::error_code& ec,
895 const sdbusplus::message::unix_fd& unixfd) {
896 downloadEntryCallback(asyncResp, entryID, dumpType, ec, unixfd);
897 };
898
899 crow::connections::systemBus->async_method_call(
900 std::move(downloadEventLogEntryHandler), "xyz.openbmc_project.Logging",
901 entryPath, "xyz.openbmc_project.Logging.Entry", "GetEntry");
902 }
903
904 inline DumpCreationProgress
mapDbusStatusToDumpProgress(const std::string & status)905 mapDbusStatusToDumpProgress(const std::string& status)
906 {
907 if (status ==
908 "xyz.openbmc_project.Common.Progress.OperationStatus.Failed" ||
909 status == "xyz.openbmc_project.Common.Progress.OperationStatus.Aborted")
910 {
911 return DumpCreationProgress::DUMP_CREATE_FAILED;
912 }
913 if (status ==
914 "xyz.openbmc_project.Common.Progress.OperationStatus.Completed")
915 {
916 return DumpCreationProgress::DUMP_CREATE_SUCCESS;
917 }
918 return DumpCreationProgress::DUMP_CREATE_INPROGRESS;
919 }
920
921 inline DumpCreationProgress
getDumpCompletionStatus(const dbus::utility::DBusPropertiesMap & values)922 getDumpCompletionStatus(const dbus::utility::DBusPropertiesMap& values)
923 {
924 for (const auto& [key, val] : values)
925 {
926 if (key == "Status")
927 {
928 const std::string* value = std::get_if<std::string>(&val);
929 if (value == nullptr)
930 {
931 BMCWEB_LOG_ERROR("Status property value is null");
932 return DumpCreationProgress::DUMP_CREATE_FAILED;
933 }
934 return mapDbusStatusToDumpProgress(*value);
935 }
936 }
937 return DumpCreationProgress::DUMP_CREATE_INPROGRESS;
938 }
939
getDumpEntryPath(const std::string & dumpPath)940 inline std::string getDumpEntryPath(const std::string& dumpPath)
941 {
942 if (dumpPath == "/xyz/openbmc_project/dump/bmc/entry")
943 {
944 return std::format("/redfish/v1/Managers/{}/LogServices/Dump/Entries/",
945 BMCWEB_REDFISH_SYSTEM_URI_NAME);
946 }
947 if (dumpPath == "/xyz/openbmc_project/dump/system/entry")
948 {
949 return std::format("/redfish/v1/Systems/{}/LogServices/Dump/Entries/",
950 BMCWEB_REDFISH_SYSTEM_URI_NAME);
951 }
952 return "";
953 }
954
createDumpTaskCallback(task::Payload && payload,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const sdbusplus::message::object_path & createdObjPath)955 inline void createDumpTaskCallback(
956 task::Payload&& payload,
957 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
958 const sdbusplus::message::object_path& createdObjPath)
959 {
960 const std::string dumpPath = createdObjPath.parent_path().str;
961 const std::string dumpId = createdObjPath.filename();
962
963 std::string dumpEntryPath = getDumpEntryPath(dumpPath);
964
965 if (dumpEntryPath.empty())
966 {
967 BMCWEB_LOG_ERROR("Invalid dump type received");
968 messages::internalError(asyncResp->res);
969 return;
970 }
971
972 crow::connections::systemBus->async_method_call(
973 [asyncResp, payload = std::move(payload), createdObjPath,
974 dumpEntryPath{std::move(dumpEntryPath)},
975 dumpId](const boost::system::error_code& ec,
976 const std::string& introspectXml) {
977 if (ec)
978 {
979 BMCWEB_LOG_ERROR("Introspect call failed with error: {}",
980 ec.message());
981 messages::internalError(asyncResp->res);
982 return;
983 }
984
985 // Check if the created dump object has implemented Progress
986 // interface to track dump completion. If yes, fetch the "Status"
987 // property of the interface, modify the task state accordingly.
988 // Else, return task completed.
989 tinyxml2::XMLDocument doc;
990
991 doc.Parse(introspectXml.data(), introspectXml.size());
992 tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
993 if (pRoot == nullptr)
994 {
995 BMCWEB_LOG_ERROR("XML document failed to parse");
996 messages::internalError(asyncResp->res);
997 return;
998 }
999 tinyxml2::XMLElement* interfaceNode =
1000 pRoot->FirstChildElement("interface");
1001
1002 bool isProgressIntfPresent = false;
1003 while (interfaceNode != nullptr)
1004 {
1005 const char* thisInterfaceName = interfaceNode->Attribute("name");
1006 if (thisInterfaceName != nullptr)
1007 {
1008 if (thisInterfaceName ==
1009 std::string_view("xyz.openbmc_project.Common.Progress"))
1010 {
1011 interfaceNode =
1012 interfaceNode->NextSiblingElement("interface");
1013 continue;
1014 }
1015 isProgressIntfPresent = true;
1016 break;
1017 }
1018 interfaceNode = interfaceNode->NextSiblingElement("interface");
1019 }
1020
1021 std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
1022 [createdObjPath, dumpEntryPath, dumpId, isProgressIntfPresent](
1023 const boost::system::error_code& ec2, sdbusplus::message_t& msg,
1024 const std::shared_ptr<task::TaskData>& taskData) {
1025 if (ec2)
1026 {
1027 BMCWEB_LOG_ERROR("{}: Error in creating dump",
1028 createdObjPath.str);
1029 taskData->messages.emplace_back(messages::internalError());
1030 taskData->state = "Cancelled";
1031 return task::completed;
1032 }
1033
1034 if (isProgressIntfPresent)
1035 {
1036 dbus::utility::DBusPropertiesMap values;
1037 std::string prop;
1038 msg.read(prop, values);
1039
1040 DumpCreationProgress dumpStatus =
1041 getDumpCompletionStatus(values);
1042 if (dumpStatus == DumpCreationProgress::DUMP_CREATE_FAILED)
1043 {
1044 BMCWEB_LOG_ERROR("{}: Error in creating dump",
1045 createdObjPath.str);
1046 taskData->state = "Cancelled";
1047 return task::completed;
1048 }
1049
1050 if (dumpStatus == DumpCreationProgress::DUMP_CREATE_INPROGRESS)
1051 {
1052 BMCWEB_LOG_DEBUG("{}: Dump creation task is in progress",
1053 createdObjPath.str);
1054 return !task::completed;
1055 }
1056 }
1057
1058 nlohmann::json retMessage = messages::success();
1059 taskData->messages.emplace_back(retMessage);
1060
1061 boost::urls::url url = boost::urls::format(
1062 "/redfish/v1/Managers/{}/LogServices/Dump/Entries/{}",
1063 BMCWEB_REDFISH_MANAGER_URI_NAME, dumpId);
1064
1065 std::string headerLoc = "Location: ";
1066 headerLoc += url.buffer();
1067
1068 taskData->payload->httpHeaders.emplace_back(std::move(headerLoc));
1069
1070 BMCWEB_LOG_DEBUG("{}: Dump creation task completed",
1071 createdObjPath.str);
1072 taskData->state = "Completed";
1073 return task::completed;
1074 },
1075 "type='signal',interface='org.freedesktop.DBus.Properties',"
1076 "member='PropertiesChanged',path='" +
1077 createdObjPath.str + "'");
1078
1079 // The task timer is set to max time limit within which the
1080 // requested dump will be collected.
1081 task->startTimer(std::chrono::minutes(6));
1082 task->populateResp(asyncResp->res);
1083 task->payload.emplace(payload);
1084 },
1085 "xyz.openbmc_project.Dump.Manager", createdObjPath,
1086 "org.freedesktop.DBus.Introspectable", "Introspect");
1087 }
1088
createDump(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req,const std::string & dumpType)1089 inline void createDump(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1090 const crow::Request& req, const std::string& dumpType)
1091 {
1092 std::string dumpPath = getDumpEntriesPath(dumpType);
1093 if (dumpPath.empty())
1094 {
1095 messages::internalError(asyncResp->res);
1096 return;
1097 }
1098
1099 std::optional<std::string> diagnosticDataType;
1100 std::optional<std::string> oemDiagnosticDataType;
1101
1102 if (!redfish::json_util::readJsonAction(
1103 req, asyncResp->res, "DiagnosticDataType", diagnosticDataType,
1104 "OEMDiagnosticDataType", oemDiagnosticDataType))
1105 {
1106 return;
1107 }
1108
1109 if (dumpType == "System")
1110 {
1111 if (!oemDiagnosticDataType || !diagnosticDataType)
1112 {
1113 BMCWEB_LOG_ERROR(
1114 "CreateDump action parameter 'DiagnosticDataType'/'OEMDiagnosticDataType' value not found!");
1115 messages::actionParameterMissing(
1116 asyncResp->res, "CollectDiagnosticData",
1117 "DiagnosticDataType & OEMDiagnosticDataType");
1118 return;
1119 }
1120 if ((*oemDiagnosticDataType != "System") ||
1121 (*diagnosticDataType != "OEM"))
1122 {
1123 BMCWEB_LOG_ERROR("Wrong parameter values passed");
1124 messages::internalError(asyncResp->res);
1125 return;
1126 }
1127 dumpPath = std::format("/redfish/v1/Systems/{}/LogServices/Dump/",
1128 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1129 }
1130 else if (dumpType == "BMC")
1131 {
1132 if (!diagnosticDataType)
1133 {
1134 BMCWEB_LOG_ERROR(
1135 "CreateDump action parameter 'DiagnosticDataType' not found!");
1136 messages::actionParameterMissing(
1137 asyncResp->res, "CollectDiagnosticData", "DiagnosticDataType");
1138 return;
1139 }
1140 if (*diagnosticDataType != "Manager")
1141 {
1142 BMCWEB_LOG_ERROR(
1143 "Wrong parameter value passed for 'DiagnosticDataType'");
1144 messages::internalError(asyncResp->res);
1145 return;
1146 }
1147 dumpPath = std::format("/redfish/v1/Managers/{}/LogServices/Dump/",
1148 BMCWEB_REDFISH_MANAGER_URI_NAME);
1149 }
1150 else
1151 {
1152 BMCWEB_LOG_ERROR("CreateDump failed. Unknown dump type");
1153 messages::internalError(asyncResp->res);
1154 return;
1155 }
1156
1157 std::vector<std::pair<std::string, std::variant<std::string, uint64_t>>>
1158 createDumpParamVec;
1159
1160 if (req.session != nullptr)
1161 {
1162 createDumpParamVec.emplace_back(
1163 "xyz.openbmc_project.Dump.Create.CreateParameters.OriginatorId",
1164 req.session->clientIp);
1165 createDumpParamVec.emplace_back(
1166 "xyz.openbmc_project.Dump.Create.CreateParameters.OriginatorType",
1167 "xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.Client");
1168 }
1169
1170 crow::connections::systemBus->async_method_call(
1171 [asyncResp, payload(task::Payload(req)),
1172 dumpPath](const boost::system::error_code& ec,
1173 const sdbusplus::message_t& msg,
1174 const sdbusplus::message::object_path& objPath) mutable {
1175 if (ec)
1176 {
1177 BMCWEB_LOG_ERROR("CreateDump resp_handler got error {}", ec);
1178 const sd_bus_error* dbusError = msg.get_error();
1179 if (dbusError == nullptr)
1180 {
1181 messages::internalError(asyncResp->res);
1182 return;
1183 }
1184
1185 BMCWEB_LOG_ERROR("CreateDump DBus error: {} and error msg: {}",
1186 dbusError->name, dbusError->message);
1187 if (std::string_view(
1188 "xyz.openbmc_project.Common.Error.NotAllowed") ==
1189 dbusError->name)
1190 {
1191 messages::resourceInStandby(asyncResp->res);
1192 return;
1193 }
1194 if (std::string_view(
1195 "xyz.openbmc_project.Dump.Create.Error.Disabled") ==
1196 dbusError->name)
1197 {
1198 messages::serviceDisabled(asyncResp->res, dumpPath);
1199 return;
1200 }
1201 if (std::string_view(
1202 "xyz.openbmc_project.Common.Error.Unavailable") ==
1203 dbusError->name)
1204 {
1205 messages::resourceInUse(asyncResp->res);
1206 return;
1207 }
1208 // Other Dbus errors such as:
1209 // xyz.openbmc_project.Common.Error.InvalidArgument &
1210 // org.freedesktop.DBus.Error.InvalidArgs are all related to
1211 // the dbus call that is made here in the bmcweb
1212 // implementation and has nothing to do with the client's
1213 // input in the request. Hence, returning internal error
1214 // back to the client.
1215 messages::internalError(asyncResp->res);
1216 return;
1217 }
1218 BMCWEB_LOG_DEBUG("Dump Created. Path: {}", objPath.str);
1219 createDumpTaskCallback(std::move(payload), asyncResp, objPath);
1220 },
1221 "xyz.openbmc_project.Dump.Manager", getDumpPath(dumpType),
1222 "xyz.openbmc_project.Dump.Create", "CreateDump", createDumpParamVec);
1223 }
1224
clearDump(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & dumpType)1225 inline void clearDump(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1226 const std::string& dumpType)
1227 {
1228 crow::connections::systemBus->async_method_call(
1229 [asyncResp](const boost::system::error_code& ec) {
1230 if (ec)
1231 {
1232 BMCWEB_LOG_ERROR("clearDump resp_handler got error {}", ec);
1233 messages::internalError(asyncResp->res);
1234 return;
1235 }
1236 },
1237 "xyz.openbmc_project.Dump.Manager", getDumpPath(dumpType),
1238 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
1239 }
1240
1241 inline void
parseCrashdumpParameters(const dbus::utility::DBusPropertiesMap & params,std::string & filename,std::string & timestamp,std::string & logfile)1242 parseCrashdumpParameters(const dbus::utility::DBusPropertiesMap& params,
1243 std::string& filename, std::string& timestamp,
1244 std::string& logfile)
1245 {
1246 const std::string* filenamePtr = nullptr;
1247 const std::string* timestampPtr = nullptr;
1248 const std::string* logfilePtr = nullptr;
1249
1250 const bool success = sdbusplus::unpackPropertiesNoThrow(
1251 dbus_utils::UnpackErrorPrinter(), params, "Timestamp", timestampPtr,
1252 "Filename", filenamePtr, "Log", logfilePtr);
1253
1254 if (!success)
1255 {
1256 return;
1257 }
1258
1259 if (filenamePtr != nullptr)
1260 {
1261 filename = *filenamePtr;
1262 }
1263
1264 if (timestampPtr != nullptr)
1265 {
1266 timestamp = *timestampPtr;
1267 }
1268
1269 if (logfilePtr != nullptr)
1270 {
1271 logfile = *logfilePtr;
1272 }
1273 }
1274
requestRoutesSystemLogServiceCollection(App & app)1275 inline void requestRoutesSystemLogServiceCollection(App& app)
1276 {
1277 /**
1278 * Functions triggers appropriate requests on DBus
1279 */
1280 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/")
1281 .privileges(redfish::privileges::getLogServiceCollection)
1282 .methods(boost::beast::http::verb::get)(
1283 [&app](const crow::Request& req,
1284 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1285 const std::string& systemName) {
1286 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1287 {
1288 return;
1289 }
1290 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1291 {
1292 // Option currently returns no systems. TBD
1293 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1294 systemName);
1295 return;
1296 }
1297 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1298 {
1299 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1300 systemName);
1301 return;
1302 }
1303
1304 // Collections don't include the static data added by SubRoute
1305 // because it has a duplicate entry for members
1306 asyncResp->res.jsonValue["@odata.type"] =
1307 "#LogServiceCollection.LogServiceCollection";
1308 asyncResp->res.jsonValue["@odata.id"] =
1309 std::format("/redfish/v1/Systems/{}/LogServices",
1310 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1311 asyncResp->res.jsonValue["Name"] = "System Log Services Collection";
1312 asyncResp->res.jsonValue["Description"] =
1313 "Collection of LogServices for this Computer System";
1314 nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"];
1315 logServiceArray = nlohmann::json::array();
1316 nlohmann::json::object_t eventLog;
1317 eventLog["@odata.id"] =
1318 std::format("/redfish/v1/Systems/{}/LogServices/EventLog",
1319 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1320 logServiceArray.emplace_back(std::move(eventLog));
1321 if constexpr (BMCWEB_REDFISH_DUMP_LOG)
1322 {
1323 nlohmann::json::object_t dumpLog;
1324 dumpLog["@odata.id"] =
1325 std::format("/redfish/v1/Systems/{}/LogServices/Dump",
1326 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1327 logServiceArray.emplace_back(std::move(dumpLog));
1328 }
1329
1330 if constexpr (BMCWEB_REDFISH_CPU_LOG)
1331 {
1332 nlohmann::json::object_t crashdump;
1333 crashdump["@odata.id"] =
1334 std::format("/redfish/v1/Systems/{}/LogServices/Crashdump",
1335 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1336 logServiceArray.emplace_back(std::move(crashdump));
1337 }
1338
1339 if constexpr (BMCWEB_REDFISH_HOST_LOGGER)
1340 {
1341 nlohmann::json::object_t hostlogger;
1342 hostlogger["@odata.id"] =
1343 std::format("/redfish/v1/Systems/{}/LogServices/HostLogger",
1344 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1345 logServiceArray.emplace_back(std::move(hostlogger));
1346 }
1347 asyncResp->res.jsonValue["Members@odata.count"] =
1348 logServiceArray.size();
1349
1350 constexpr std::array<std::string_view, 1> interfaces = {
1351 "xyz.openbmc_project.State.Boot.PostCode"};
1352 dbus::utility::getSubTreePaths(
1353 "/", 0, interfaces,
1354 [asyncResp](const boost::system::error_code& ec,
1355 const dbus::utility::MapperGetSubTreePathsResponse&
1356 subtreePath) {
1357 if (ec)
1358 {
1359 BMCWEB_LOG_ERROR("{}", ec);
1360 return;
1361 }
1362
1363 for (const auto& pathStr : subtreePath)
1364 {
1365 if (pathStr.find("PostCode") != std::string::npos)
1366 {
1367 nlohmann::json& logServiceArrayLocal =
1368 asyncResp->res.jsonValue["Members"];
1369 nlohmann::json::object_t member;
1370 member["@odata.id"] = std::format(
1371 "/redfish/v1/Systems/{}/LogServices/PostCodes",
1372 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1373
1374 logServiceArrayLocal.emplace_back(std::move(member));
1375
1376 asyncResp->res.jsonValue["Members@odata.count"] =
1377 logServiceArrayLocal.size();
1378 return;
1379 }
1380 }
1381 });
1382 });
1383 }
1384
requestRoutesEventLogService(App & app)1385 inline void requestRoutesEventLogService(App& app)
1386 {
1387 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/")
1388 .privileges(redfish::privileges::getLogService)
1389 .methods(boost::beast::http::verb::get)(
1390 [&app](const crow::Request& req,
1391 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1392 const std::string& systemName) {
1393 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1394 {
1395 return;
1396 }
1397 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1398 {
1399 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1400 systemName);
1401 return;
1402 }
1403 asyncResp->res.jsonValue["@odata.id"] =
1404 std::format("/redfish/v1/Systems/{}/LogServices/EventLog",
1405 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1406 asyncResp->res.jsonValue["@odata.type"] =
1407 "#LogService.v1_2_0.LogService";
1408 asyncResp->res.jsonValue["Name"] = "Event Log Service";
1409 asyncResp->res.jsonValue["Description"] = "System Event Log Service";
1410 asyncResp->res.jsonValue["Id"] = "EventLog";
1411 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
1412
1413 std::pair<std::string, std::string> redfishDateTimeOffset =
1414 redfish::time_utils::getDateTimeOffsetNow();
1415
1416 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
1417 asyncResp->res.jsonValue["DateTimeLocalOffset"] =
1418 redfishDateTimeOffset.second;
1419
1420 asyncResp->res.jsonValue["Entries"]["@odata.id"] =
1421 std::format("/redfish/v1/Systems/{}/LogServices/EventLog/Entries",
1422 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1423 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]["target"]
1424
1425 = std::format(
1426 "/redfish/v1/Systems/{}/LogServices/EventLog/Actions/LogService.ClearLog",
1427 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1428 });
1429 }
1430
requestRoutesJournalEventLogClear(App & app)1431 inline void requestRoutesJournalEventLogClear(App& app)
1432 {
1433 BMCWEB_ROUTE(
1434 app,
1435 "/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/")
1436 .privileges({{"ConfigureComponents"}})
1437 .methods(boost::beast::http::verb::post)(
1438 [&app](const crow::Request& req,
1439 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1440 const std::string& systemName) {
1441 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1442 {
1443 return;
1444 }
1445 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1446 {
1447 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1448 systemName);
1449 return;
1450 }
1451 // Clear the EventLog by deleting the log files
1452 std::vector<std::filesystem::path> redfishLogFiles;
1453 if (getRedfishLogFiles(redfishLogFiles))
1454 {
1455 for (const std::filesystem::path& file : redfishLogFiles)
1456 {
1457 std::error_code ec;
1458 std::filesystem::remove(file, ec);
1459 }
1460 }
1461
1462 // Reload rsyslog so it knows to start new log files
1463 crow::connections::systemBus->async_method_call(
1464 [asyncResp](const boost::system::error_code& ec) {
1465 if (ec)
1466 {
1467 BMCWEB_LOG_ERROR("Failed to reload rsyslog: {}", ec);
1468 messages::internalError(asyncResp->res);
1469 return;
1470 }
1471
1472 messages::success(asyncResp->res);
1473 },
1474 "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
1475 "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service",
1476 "replace");
1477 });
1478 }
1479
1480 enum class LogParseError
1481 {
1482 success,
1483 parseFailed,
1484 messageIdNotInRegistry,
1485 };
1486
1487 static LogParseError
fillEventLogEntryJson(const std::string & logEntryID,const std::string & logEntry,nlohmann::json::object_t & logEntryJson)1488 fillEventLogEntryJson(const std::string& logEntryID,
1489 const std::string& logEntry,
1490 nlohmann::json::object_t& logEntryJson)
1491 {
1492 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
1493 // First get the Timestamp
1494 size_t space = logEntry.find_first_of(' ');
1495 if (space == std::string::npos)
1496 {
1497 return LogParseError::parseFailed;
1498 }
1499 std::string timestamp = logEntry.substr(0, space);
1500 // Then get the log contents
1501 size_t entryStart = logEntry.find_first_not_of(' ', space);
1502 if (entryStart == std::string::npos)
1503 {
1504 return LogParseError::parseFailed;
1505 }
1506 std::string_view entry(logEntry);
1507 entry.remove_prefix(entryStart);
1508 // Use split to separate the entry into its fields
1509 std::vector<std::string> logEntryFields;
1510 bmcweb::split(logEntryFields, entry, ',');
1511 // We need at least a MessageId to be valid
1512 auto logEntryIter = logEntryFields.begin();
1513 if (logEntryIter == logEntryFields.end())
1514 {
1515 return LogParseError::parseFailed;
1516 }
1517 std::string& messageID = *logEntryIter;
1518 // Get the Message from the MessageRegistry
1519 const registries::Message* message = registries::getMessage(messageID);
1520
1521 logEntryIter++;
1522 if (message == nullptr)
1523 {
1524 BMCWEB_LOG_WARNING("Log entry not found in registry: {}", logEntry);
1525 return LogParseError::messageIdNotInRegistry;
1526 }
1527
1528 std::vector<std::string_view> messageArgs(logEntryIter,
1529 logEntryFields.end());
1530 messageArgs.resize(message->numberOfArgs);
1531
1532 std::string msg = redfish::registries::fillMessageArgs(messageArgs,
1533 message->message);
1534 if (msg.empty())
1535 {
1536 return LogParseError::parseFailed;
1537 }
1538
1539 // Get the Created time from the timestamp. The log timestamp is in RFC3339
1540 // format which matches the Redfish format except for the fractional seconds
1541 // between the '.' and the '+', so just remove them.
1542 std::size_t dot = timestamp.find_first_of('.');
1543 std::size_t plus = timestamp.find_first_of('+');
1544 if (dot != std::string::npos && plus != std::string::npos)
1545 {
1546 timestamp.erase(dot, plus - dot);
1547 }
1548
1549 // Fill in the log entry with the gathered data
1550 logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
1551 logEntryJson["@odata.id"] = boost::urls::format(
1552 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}",
1553 BMCWEB_REDFISH_SYSTEM_URI_NAME, logEntryID);
1554 logEntryJson["Name"] = "System Event Log Entry";
1555 logEntryJson["Id"] = logEntryID;
1556 logEntryJson["Message"] = std::move(msg);
1557 logEntryJson["MessageId"] = std::move(messageID);
1558 logEntryJson["MessageArgs"] = messageArgs;
1559 logEntryJson["EntryType"] = "Event";
1560 logEntryJson["Severity"] = message->messageSeverity;
1561 logEntryJson["Created"] = std::move(timestamp);
1562 return LogParseError::success;
1563 }
1564
requestRoutesJournalEventLogEntryCollection(App & app)1565 inline void requestRoutesJournalEventLogEntryCollection(App& app)
1566 {
1567 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/")
1568 .privileges(redfish::privileges::getLogEntryCollection)
1569 .methods(boost::beast::http::verb::get)(
1570 [&app](const crow::Request& req,
1571 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1572 const std::string& systemName) {
1573 query_param::QueryCapabilities capabilities = {
1574 .canDelegateTop = true,
1575 .canDelegateSkip = true,
1576 };
1577 query_param::Query delegatedQuery;
1578 if (!redfish::setUpRedfishRouteWithDelegation(
1579 app, req, asyncResp, delegatedQuery, capabilities))
1580 {
1581 return;
1582 }
1583 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1584 {
1585 // Option currently returns no systems. TBD
1586 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1587 systemName);
1588 return;
1589 }
1590 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1591 {
1592 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1593 systemName);
1594 return;
1595 }
1596
1597 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
1598 size_t skip = delegatedQuery.skip.value_or(0);
1599
1600 // Collections don't include the static data added by SubRoute
1601 // because it has a duplicate entry for members
1602 asyncResp->res.jsonValue["@odata.type"] =
1603 "#LogEntryCollection.LogEntryCollection";
1604 asyncResp->res.jsonValue["@odata.id"] =
1605 std::format("/redfish/v1/Systems/{}/LogServices/EventLog/Entries",
1606 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1607 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
1608 asyncResp->res.jsonValue["Description"] =
1609 "Collection of System Event Log Entries";
1610
1611 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
1612 logEntryArray = nlohmann::json::array();
1613 // Go through the log files and create a unique ID for each
1614 // entry
1615 std::vector<std::filesystem::path> redfishLogFiles;
1616 getRedfishLogFiles(redfishLogFiles);
1617 uint64_t entryCount = 0;
1618 std::string logEntry;
1619
1620 // Oldest logs are in the last file, so start there and loop
1621 // backwards
1622 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend();
1623 it++)
1624 {
1625 std::ifstream logStream(*it);
1626 if (!logStream.is_open())
1627 {
1628 continue;
1629 }
1630
1631 // Reset the unique ID on the first entry
1632 bool firstEntry = true;
1633 while (std::getline(logStream, logEntry))
1634 {
1635 std::string idStr;
1636 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
1637 {
1638 continue;
1639 }
1640 firstEntry = false;
1641
1642 nlohmann::json::object_t bmcLogEntry;
1643 LogParseError status = fillEventLogEntryJson(idStr, logEntry,
1644 bmcLogEntry);
1645 if (status == LogParseError::messageIdNotInRegistry)
1646 {
1647 continue;
1648 }
1649 if (status != LogParseError::success)
1650 {
1651 messages::internalError(asyncResp->res);
1652 return;
1653 }
1654
1655 entryCount++;
1656 // Handle paging using skip (number of entries to skip from the
1657 // start) and top (number of entries to display)
1658 if (entryCount <= skip || entryCount > skip + top)
1659 {
1660 continue;
1661 }
1662
1663 logEntryArray.emplace_back(std::move(bmcLogEntry));
1664 }
1665 }
1666 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
1667 if (skip + top < entryCount)
1668 {
1669 asyncResp->res
1670 .jsonValue["Members@odata.nextLink"] = boost::urls::format(
1671 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries?$skip={}",
1672 BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(skip + top));
1673 }
1674 });
1675 }
1676
requestRoutesJournalEventLogEntry(App & app)1677 inline void requestRoutesJournalEventLogEntry(App& app)
1678 {
1679 BMCWEB_ROUTE(
1680 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
1681 .privileges(redfish::privileges::getLogEntry)
1682 .methods(boost::beast::http::verb::get)(
1683 [&app](const crow::Request& req,
1684 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1685 const std::string& systemName, const std::string& param) {
1686 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1687 {
1688 return;
1689 }
1690 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1691 {
1692 // Option currently returns no systems. TBD
1693 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1694 systemName);
1695 return;
1696 }
1697
1698 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1699 {
1700 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1701 systemName);
1702 return;
1703 }
1704
1705 const std::string& targetID = param;
1706
1707 // Go through the log files and check the unique ID for each
1708 // entry to find the target entry
1709 std::vector<std::filesystem::path> redfishLogFiles;
1710 getRedfishLogFiles(redfishLogFiles);
1711 std::string logEntry;
1712
1713 // Oldest logs are in the last file, so start there and loop
1714 // backwards
1715 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend();
1716 it++)
1717 {
1718 std::ifstream logStream(*it);
1719 if (!logStream.is_open())
1720 {
1721 continue;
1722 }
1723
1724 // Reset the unique ID on the first entry
1725 bool firstEntry = true;
1726 while (std::getline(logStream, logEntry))
1727 {
1728 std::string idStr;
1729 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
1730 {
1731 continue;
1732 }
1733 firstEntry = false;
1734
1735 if (idStr == targetID)
1736 {
1737 nlohmann::json::object_t bmcLogEntry;
1738 LogParseError status =
1739 fillEventLogEntryJson(idStr, logEntry, bmcLogEntry);
1740 if (status != LogParseError::success)
1741 {
1742 messages::internalError(asyncResp->res);
1743 return;
1744 }
1745 asyncResp->res.jsonValue.update(bmcLogEntry);
1746 return;
1747 }
1748 }
1749 }
1750 // Requested ID was not found
1751 messages::resourceNotFound(asyncResp->res, "LogEntry", targetID);
1752 });
1753 }
1754
requestRoutesDBusEventLogEntryCollection(App & app)1755 inline void requestRoutesDBusEventLogEntryCollection(App& app)
1756 {
1757 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/")
1758 .privileges(redfish::privileges::getLogEntryCollection)
1759 .methods(boost::beast::http::verb::get)(
1760 [&app](const crow::Request& req,
1761 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1762 const std::string& systemName) {
1763 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1764 {
1765 return;
1766 }
1767 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1768 {
1769 // Option currently returns no systems. TBD
1770 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1771 systemName);
1772 return;
1773 }
1774 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1775 {
1776 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1777 systemName);
1778 return;
1779 }
1780
1781 // Collections don't include the static data added by SubRoute
1782 // because it has a duplicate entry for members
1783 asyncResp->res.jsonValue["@odata.type"] =
1784 "#LogEntryCollection.LogEntryCollection";
1785 asyncResp->res.jsonValue["@odata.id"] =
1786 std::format("/redfish/v1/Systems/{}/LogServices/EventLog/Entries",
1787 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1788 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
1789 asyncResp->res.jsonValue["Description"] =
1790 "Collection of System Event Log Entries";
1791
1792 // DBus implementation of EventLog/Entries
1793 // Make call to Logging Service to find all log entry objects
1794 sdbusplus::message::object_path path("/xyz/openbmc_project/logging");
1795 dbus::utility::getManagedObjects(
1796 "xyz.openbmc_project.Logging", path,
1797 [asyncResp](const boost::system::error_code& ec,
1798 const dbus::utility::ManagedObjectType& resp) {
1799 if (ec)
1800 {
1801 // TODO Handle for specific error code
1802 BMCWEB_LOG_ERROR(
1803 "getLogEntriesIfaceData resp_handler got error {}", ec);
1804 messages::internalError(asyncResp->res);
1805 return;
1806 }
1807 nlohmann::json::array_t entriesArray;
1808 for (const auto& objectPath : resp)
1809 {
1810 const uint32_t* id = nullptr;
1811 const uint64_t* timestamp = nullptr;
1812 const uint64_t* updateTimestamp = nullptr;
1813 const std::string* severity = nullptr;
1814 const std::string* message = nullptr;
1815 const std::string* filePath = nullptr;
1816 const std::string* resolution = nullptr;
1817 bool resolved = false;
1818 const std::string* notify = nullptr;
1819
1820 for (const auto& interfaceMap : objectPath.second)
1821 {
1822 if (interfaceMap.first ==
1823 "xyz.openbmc_project.Logging.Entry")
1824 {
1825 for (const auto& propertyMap : interfaceMap.second)
1826 {
1827 if (propertyMap.first == "Id")
1828 {
1829 id = std::get_if<uint32_t>(&propertyMap.second);
1830 }
1831 else if (propertyMap.first == "Timestamp")
1832 {
1833 timestamp =
1834 std::get_if<uint64_t>(&propertyMap.second);
1835 }
1836 else if (propertyMap.first == "UpdateTimestamp")
1837 {
1838 updateTimestamp =
1839 std::get_if<uint64_t>(&propertyMap.second);
1840 }
1841 else if (propertyMap.first == "Severity")
1842 {
1843 severity = std::get_if<std::string>(
1844 &propertyMap.second);
1845 }
1846 else if (propertyMap.first == "Resolution")
1847 {
1848 resolution = std::get_if<std::string>(
1849 &propertyMap.second);
1850 }
1851 else if (propertyMap.first == "Message")
1852 {
1853 message = std::get_if<std::string>(
1854 &propertyMap.second);
1855 }
1856 else if (propertyMap.first == "Resolved")
1857 {
1858 const bool* resolveptr =
1859 std::get_if<bool>(&propertyMap.second);
1860 if (resolveptr == nullptr)
1861 {
1862 messages::internalError(asyncResp->res);
1863 return;
1864 }
1865 resolved = *resolveptr;
1866 }
1867 else if (propertyMap.first ==
1868 "ServiceProviderNotify")
1869 {
1870 notify = std::get_if<std::string>(
1871 &propertyMap.second);
1872 if (notify == nullptr)
1873 {
1874 messages::internalError(asyncResp->res);
1875 return;
1876 }
1877 }
1878 }
1879 if (id == nullptr || message == nullptr ||
1880 severity == nullptr)
1881 {
1882 messages::internalError(asyncResp->res);
1883 return;
1884 }
1885 }
1886 else if (interfaceMap.first ==
1887 "xyz.openbmc_project.Common.FilePath")
1888 {
1889 for (const auto& propertyMap : interfaceMap.second)
1890 {
1891 if (propertyMap.first == "Path")
1892 {
1893 filePath = std::get_if<std::string>(
1894 &propertyMap.second);
1895 }
1896 }
1897 }
1898 }
1899 // Object path without the
1900 // xyz.openbmc_project.Logging.Entry interface, ignore
1901 // and continue.
1902 if (id == nullptr || message == nullptr ||
1903 severity == nullptr || timestamp == nullptr ||
1904 updateTimestamp == nullptr)
1905 {
1906 continue;
1907 }
1908 nlohmann::json& thisEntry = entriesArray.emplace_back();
1909 thisEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
1910 thisEntry["@odata.id"] = boost::urls::format(
1911 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}",
1912 BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(*id));
1913 thisEntry["Name"] = "System Event Log Entry";
1914 thisEntry["Id"] = std::to_string(*id);
1915 thisEntry["Message"] = *message;
1916 thisEntry["Resolved"] = resolved;
1917 if ((resolution != nullptr) && (!(*resolution).empty()))
1918 {
1919 thisEntry["Resolution"] = *resolution;
1920 }
1921 std::optional<bool> notifyAction =
1922 getProviderNotifyAction(*notify);
1923 if (notifyAction)
1924 {
1925 thisEntry["ServiceProviderNotified"] = *notifyAction;
1926 }
1927 thisEntry["EntryType"] = "Event";
1928 thisEntry["Severity"] =
1929 translateSeverityDbusToRedfish(*severity);
1930 thisEntry["Created"] =
1931 redfish::time_utils::getDateTimeUintMs(*timestamp);
1932 thisEntry["Modified"] =
1933 redfish::time_utils::getDateTimeUintMs(*updateTimestamp);
1934 if (filePath != nullptr)
1935 {
1936 thisEntry["AdditionalDataURI"] =
1937 std::format(
1938 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/",
1939 BMCWEB_REDFISH_SYSTEM_URI_NAME) +
1940 std::to_string(*id) + "/attachment";
1941 }
1942 }
1943 std::ranges::sort(entriesArray, [](const nlohmann::json& left,
1944 const nlohmann::json& right) {
1945 return (left["Id"] <= right["Id"]);
1946 });
1947 asyncResp->res.jsonValue["Members@odata.count"] =
1948 entriesArray.size();
1949 asyncResp->res.jsonValue["Members"] = std::move(entriesArray);
1950 });
1951 });
1952 }
1953
requestRoutesDBusEventLogEntry(App & app)1954 inline void requestRoutesDBusEventLogEntry(App& app)
1955 {
1956 BMCWEB_ROUTE(
1957 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
1958 .privileges(redfish::privileges::getLogEntry)
1959 .methods(boost::beast::http::verb::get)(
1960 [&app](const crow::Request& req,
1961 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1962 const std::string& systemName, const std::string& param) {
1963 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1964 {
1965 return;
1966 }
1967 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1968 {
1969 // Option currently returns no systems. TBD
1970 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1971 systemName);
1972 return;
1973 }
1974 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1975 {
1976 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1977 systemName);
1978 return;
1979 }
1980
1981 std::string entryID = param;
1982 dbus::utility::escapePathForDbus(entryID);
1983
1984 // DBus implementation of EventLog/Entries
1985 // Make call to Logging Service to find all log entry objects
1986 sdbusplus::asio::getAllProperties(
1987 *crow::connections::systemBus, "xyz.openbmc_project.Logging",
1988 "/xyz/openbmc_project/logging/entry/" + entryID, "",
1989 [asyncResp, entryID](const boost::system::error_code& ec,
1990 const dbus::utility::DBusPropertiesMap& resp) {
1991 if (ec.value() == EBADR)
1992 {
1993 messages::resourceNotFound(asyncResp->res, "EventLogEntry",
1994 entryID);
1995 return;
1996 }
1997 if (ec)
1998 {
1999 BMCWEB_LOG_ERROR(
2000 "EventLogEntry (DBus) resp_handler got error {}", ec);
2001 messages::internalError(asyncResp->res);
2002 return;
2003 }
2004 const uint32_t* id = nullptr;
2005 const uint64_t* timestamp = nullptr;
2006 const uint64_t* updateTimestamp = nullptr;
2007 const std::string* severity = nullptr;
2008 const std::string* message = nullptr;
2009 const std::string* filePath = nullptr;
2010 const std::string* resolution = nullptr;
2011 bool resolved = false;
2012 const std::string* notify = nullptr;
2013
2014 const bool success = sdbusplus::unpackPropertiesNoThrow(
2015 dbus_utils::UnpackErrorPrinter(), resp, "Id", id, "Timestamp",
2016 timestamp, "UpdateTimestamp", updateTimestamp, "Severity",
2017 severity, "Message", message, "Resolved", resolved,
2018 "Resolution", resolution, "Path", filePath,
2019 "ServiceProviderNotify", notify);
2020
2021 if (!success)
2022 {
2023 messages::internalError(asyncResp->res);
2024 return;
2025 }
2026
2027 if (id == nullptr || message == nullptr || severity == nullptr ||
2028 timestamp == nullptr || updateTimestamp == nullptr ||
2029 notify == nullptr)
2030 {
2031 messages::internalError(asyncResp->res);
2032 return;
2033 }
2034
2035 asyncResp->res.jsonValue["@odata.type"] =
2036 "#LogEntry.v1_9_0.LogEntry";
2037 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
2038 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}",
2039 BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(*id));
2040 asyncResp->res.jsonValue["Name"] = "System Event Log Entry";
2041 asyncResp->res.jsonValue["Id"] = std::to_string(*id);
2042 asyncResp->res.jsonValue["Message"] = *message;
2043 asyncResp->res.jsonValue["Resolved"] = resolved;
2044 std::optional<bool> notifyAction = getProviderNotifyAction(*notify);
2045 if (notifyAction)
2046 {
2047 asyncResp->res.jsonValue["ServiceProviderNotified"] =
2048 *notifyAction;
2049 }
2050 if ((resolution != nullptr) && (!(*resolution).empty()))
2051 {
2052 asyncResp->res.jsonValue["Resolution"] = *resolution;
2053 }
2054 asyncResp->res.jsonValue["EntryType"] = "Event";
2055 asyncResp->res.jsonValue["Severity"] =
2056 translateSeverityDbusToRedfish(*severity);
2057 asyncResp->res.jsonValue["Created"] =
2058 redfish::time_utils::getDateTimeUintMs(*timestamp);
2059 asyncResp->res.jsonValue["Modified"] =
2060 redfish::time_utils::getDateTimeUintMs(*updateTimestamp);
2061 if (filePath != nullptr)
2062 {
2063 asyncResp->res.jsonValue["AdditionalDataURI"] =
2064 std::format(
2065 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/",
2066 BMCWEB_REDFISH_SYSTEM_URI_NAME) +
2067 std::to_string(*id) + "/attachment";
2068 }
2069 });
2070 });
2071
2072 BMCWEB_ROUTE(
2073 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
2074 .privileges(redfish::privileges::patchLogEntry)
2075 .methods(boost::beast::http::verb::patch)(
2076 [&app](const crow::Request& req,
2077 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2078 const std::string& systemName, const std::string& entryId) {
2079 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2080 {
2081 return;
2082 }
2083 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2084 {
2085 // Option currently returns no systems. TBD
2086 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2087 systemName);
2088 return;
2089 }
2090 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2091 {
2092 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2093 systemName);
2094 return;
2095 }
2096 std::optional<bool> resolved;
2097
2098 if (!json_util::readJsonPatch(req, asyncResp->res, "Resolved",
2099 resolved))
2100 {
2101 return;
2102 }
2103 BMCWEB_LOG_DEBUG("Set Resolved");
2104
2105 setDbusProperty(asyncResp, "Resolved", "xyz.openbmc_project.Logging",
2106 "/xyz/openbmc_project/logging/entry/" + entryId,
2107 "xyz.openbmc_project.Logging.Entry", "Resolved",
2108 *resolved);
2109 });
2110
2111 BMCWEB_ROUTE(
2112 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
2113 .privileges(redfish::privileges::deleteLogEntry)
2114
2115 .methods(boost::beast::http::verb::delete_)(
2116 [&app](const crow::Request& req,
2117 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2118 const std::string& systemName, const std::string& param) {
2119 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2120 {
2121 return;
2122 }
2123 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2124 {
2125 // Option currently returns no systems. TBD
2126 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2127 systemName);
2128 return;
2129 }
2130 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2131 {
2132 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2133 systemName);
2134 return;
2135 }
2136 BMCWEB_LOG_DEBUG("Do delete single event entries.");
2137
2138 std::string entryID = param;
2139
2140 dbus::utility::escapePathForDbus(entryID);
2141
2142 // Process response from Logging service.
2143 auto respHandler = [asyncResp,
2144 entryID](const boost::system::error_code& ec) {
2145 BMCWEB_LOG_DEBUG("EventLogEntry (DBus) doDelete callback: Done");
2146 if (ec)
2147 {
2148 if (ec.value() == EBADR)
2149 {
2150 messages::resourceNotFound(asyncResp->res, "LogEntry",
2151 entryID);
2152 return;
2153 }
2154 // TODO Handle for specific error code
2155 BMCWEB_LOG_ERROR(
2156 "EventLogEntry (DBus) doDelete respHandler got error {}",
2157 ec);
2158 asyncResp->res.result(
2159 boost::beast::http::status::internal_server_error);
2160 return;
2161 }
2162
2163 asyncResp->res.result(boost::beast::http::status::ok);
2164 };
2165
2166 // Make call to Logging service to request Delete Log
2167 crow::connections::systemBus->async_method_call(
2168 respHandler, "xyz.openbmc_project.Logging",
2169 "/xyz/openbmc_project/logging/entry/" + entryID,
2170 "xyz.openbmc_project.Object.Delete", "Delete");
2171 });
2172 }
2173
2174 constexpr const char* hostLoggerFolderPath = "/var/log/console";
2175
2176 inline bool
getHostLoggerFiles(const std::string & hostLoggerFilePath,std::vector<std::filesystem::path> & hostLoggerFiles)2177 getHostLoggerFiles(const std::string& hostLoggerFilePath,
2178 std::vector<std::filesystem::path>& hostLoggerFiles)
2179 {
2180 std::error_code ec;
2181 std::filesystem::directory_iterator logPath(hostLoggerFilePath, ec);
2182 if (ec)
2183 {
2184 BMCWEB_LOG_WARNING("{}", ec.message());
2185 return false;
2186 }
2187 for (const std::filesystem::directory_entry& it : logPath)
2188 {
2189 std::string filename = it.path().filename();
2190 // Prefix of each log files is "log". Find the file and save the
2191 // path
2192 if (filename.starts_with("log"))
2193 {
2194 hostLoggerFiles.emplace_back(it.path());
2195 }
2196 }
2197 // As the log files rotate, they are appended with a ".#" that is higher for
2198 // the older logs. Since we start from oldest logs, sort the name in
2199 // descending order.
2200 std::sort(hostLoggerFiles.rbegin(), hostLoggerFiles.rend(),
2201 AlphanumLess<std::string>());
2202
2203 return true;
2204 }
2205
getHostLoggerEntries(const std::vector<std::filesystem::path> & hostLoggerFiles,uint64_t skip,uint64_t top,std::vector<std::string> & logEntries,size_t & logCount)2206 inline bool getHostLoggerEntries(
2207 const std::vector<std::filesystem::path>& hostLoggerFiles, uint64_t skip,
2208 uint64_t top, std::vector<std::string>& logEntries, size_t& logCount)
2209 {
2210 GzFileReader logFile;
2211
2212 // Go though all log files and expose host logs.
2213 for (const std::filesystem::path& it : hostLoggerFiles)
2214 {
2215 if (!logFile.gzGetLines(it.string(), skip, top, logEntries, logCount))
2216 {
2217 BMCWEB_LOG_ERROR("fail to expose host logs");
2218 return false;
2219 }
2220 }
2221 // Get lastMessage from constructor by getter
2222 std::string lastMessage = logFile.getLastMessage();
2223 if (!lastMessage.empty())
2224 {
2225 logCount++;
2226 if (logCount > skip && logCount <= (skip + top))
2227 {
2228 logEntries.push_back(lastMessage);
2229 }
2230 }
2231 return true;
2232 }
2233
fillHostLoggerEntryJson(std::string_view logEntryID,std::string_view msg,nlohmann::json::object_t & logEntryJson)2234 inline void fillHostLoggerEntryJson(std::string_view logEntryID,
2235 std::string_view msg,
2236 nlohmann::json::object_t& logEntryJson)
2237 {
2238 // Fill in the log entry with the gathered data.
2239 logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
2240 logEntryJson["@odata.id"] = boost::urls::format(
2241 "/redfish/v1/Systems/{}/LogServices/HostLogger/Entries/{}",
2242 BMCWEB_REDFISH_SYSTEM_URI_NAME, logEntryID);
2243 logEntryJson["Name"] = "Host Logger Entry";
2244 logEntryJson["Id"] = logEntryID;
2245 logEntryJson["Message"] = msg;
2246 logEntryJson["EntryType"] = "Oem";
2247 logEntryJson["Severity"] = "OK";
2248 logEntryJson["OemRecordFormat"] = "Host Logger Entry";
2249 }
2250
requestRoutesSystemHostLogger(App & app)2251 inline void requestRoutesSystemHostLogger(App& app)
2252 {
2253 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/")
2254 .privileges(redfish::privileges::getLogService)
2255 .methods(boost::beast::http::verb::get)(
2256 [&app](const crow::Request& req,
2257 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2258 const std::string& systemName) {
2259 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2260 {
2261 return;
2262 }
2263 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2264 {
2265 // Option currently returns no systems. TBD
2266 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2267 systemName);
2268 return;
2269 }
2270 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2271 {
2272 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2273 systemName);
2274 return;
2275 }
2276 asyncResp->res.jsonValue["@odata.id"] =
2277 std::format("/redfish/v1/Systems/{}/LogServices/HostLogger",
2278 BMCWEB_REDFISH_SYSTEM_URI_NAME);
2279 asyncResp->res.jsonValue["@odata.type"] =
2280 "#LogService.v1_2_0.LogService";
2281 asyncResp->res.jsonValue["Name"] = "Host Logger Service";
2282 asyncResp->res.jsonValue["Description"] = "Host Logger Service";
2283 asyncResp->res.jsonValue["Id"] = "HostLogger";
2284 asyncResp->res.jsonValue["Entries"]["@odata.id"] =
2285 std::format("/redfish/v1/Systems/{}/LogServices/HostLogger/Entries",
2286 BMCWEB_REDFISH_SYSTEM_URI_NAME);
2287 });
2288 }
2289
requestRoutesSystemHostLoggerCollection(App & app)2290 inline void requestRoutesSystemHostLoggerCollection(App& app)
2291 {
2292 BMCWEB_ROUTE(app,
2293 "/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/")
2294 .privileges(redfish::privileges::getLogEntry)
2295 .methods(boost::beast::http::verb::get)(
2296 [&app](const crow::Request& req,
2297 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2298 const std::string& systemName) {
2299 query_param::QueryCapabilities capabilities = {
2300 .canDelegateTop = true,
2301 .canDelegateSkip = true,
2302 };
2303 query_param::Query delegatedQuery;
2304 if (!redfish::setUpRedfishRouteWithDelegation(
2305 app, req, asyncResp, delegatedQuery, capabilities))
2306 {
2307 return;
2308 }
2309 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2310 {
2311 // Option currently returns no systems. TBD
2312 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2313 systemName);
2314 return;
2315 }
2316 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2317 {
2318 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2319 systemName);
2320 return;
2321 }
2322 asyncResp->res.jsonValue["@odata.id"] =
2323 std::format("/redfish/v1/Systems/{}/LogServices/HostLogger/Entries",
2324 BMCWEB_REDFISH_SYSTEM_URI_NAME);
2325 asyncResp->res.jsonValue["@odata.type"] =
2326 "#LogEntryCollection.LogEntryCollection";
2327 asyncResp->res.jsonValue["Name"] = "HostLogger Entries";
2328 asyncResp->res.jsonValue["Description"] =
2329 "Collection of HostLogger Entries";
2330 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
2331 logEntryArray = nlohmann::json::array();
2332 asyncResp->res.jsonValue["Members@odata.count"] = 0;
2333
2334 std::vector<std::filesystem::path> hostLoggerFiles;
2335 if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles))
2336 {
2337 BMCWEB_LOG_DEBUG("Failed to get host log file path");
2338 return;
2339 }
2340 // If we weren't provided top and skip limits, use the defaults.
2341 size_t skip = delegatedQuery.skip.value_or(0);
2342 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
2343 size_t logCount = 0;
2344 // This vector only store the entries we want to expose that
2345 // control by skip and top.
2346 std::vector<std::string> logEntries;
2347 if (!getHostLoggerEntries(hostLoggerFiles, skip, top, logEntries,
2348 logCount))
2349 {
2350 messages::internalError(asyncResp->res);
2351 return;
2352 }
2353 // If vector is empty, that means skip value larger than total
2354 // log count
2355 if (logEntries.empty())
2356 {
2357 asyncResp->res.jsonValue["Members@odata.count"] = logCount;
2358 return;
2359 }
2360 if (!logEntries.empty())
2361 {
2362 for (size_t i = 0; i < logEntries.size(); i++)
2363 {
2364 nlohmann::json::object_t hostLogEntry;
2365 fillHostLoggerEntryJson(std::to_string(skip + i), logEntries[i],
2366 hostLogEntry);
2367 logEntryArray.emplace_back(std::move(hostLogEntry));
2368 }
2369
2370 asyncResp->res.jsonValue["Members@odata.count"] = logCount;
2371 if (skip + top < logCount)
2372 {
2373 asyncResp->res.jsonValue["Members@odata.nextLink"] =
2374 std::format(
2375 "/redfish/v1/Systems/{}/LogServices/HostLogger/Entries?$skip=",
2376 BMCWEB_REDFISH_SYSTEM_URI_NAME) +
2377 std::to_string(skip + top);
2378 }
2379 }
2380 });
2381 }
2382
requestRoutesSystemHostLoggerLogEntry(App & app)2383 inline void requestRoutesSystemHostLoggerLogEntry(App& app)
2384 {
2385 BMCWEB_ROUTE(
2386 app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/<str>/")
2387 .privileges(redfish::privileges::getLogEntry)
2388 .methods(boost::beast::http::verb::get)(
2389 [&app](const crow::Request& req,
2390 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2391 const std::string& systemName, const std::string& param) {
2392 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2393 {
2394 return;
2395 }
2396 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2397 {
2398 // Option currently returns no systems. TBD
2399 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2400 systemName);
2401 return;
2402 }
2403 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2404 {
2405 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2406 systemName);
2407 return;
2408 }
2409 std::string_view targetID = param;
2410
2411 uint64_t idInt = 0;
2412
2413 auto [ptr, ec] = std::from_chars(targetID.begin(), targetID.end(),
2414 idInt);
2415 if (ec != std::errc{} || ptr != targetID.end())
2416 {
2417 messages::resourceNotFound(asyncResp->res, "LogEntry", param);
2418 return;
2419 }
2420
2421 std::vector<std::filesystem::path> hostLoggerFiles;
2422 if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles))
2423 {
2424 BMCWEB_LOG_DEBUG("Failed to get host log file path");
2425 return;
2426 }
2427
2428 size_t logCount = 0;
2429 size_t top = 1;
2430 std::vector<std::string> logEntries;
2431 // We can get specific entry by skip and top. For example, if we
2432 // want to get nth entry, we can set skip = n-1 and top = 1 to
2433 // get that entry
2434 if (!getHostLoggerEntries(hostLoggerFiles, idInt, top, logEntries,
2435 logCount))
2436 {
2437 messages::internalError(asyncResp->res);
2438 return;
2439 }
2440
2441 if (!logEntries.empty())
2442 {
2443 nlohmann::json::object_t hostLogEntry;
2444 fillHostLoggerEntryJson(targetID, logEntries[0], hostLogEntry);
2445 asyncResp->res.jsonValue.update(hostLogEntry);
2446 return;
2447 }
2448
2449 // Requested ID was not found
2450 messages::resourceNotFound(asyncResp->res, "LogEntry", param);
2451 });
2452 }
2453
handleBMCLogServicesCollectionGet(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)2454 inline void handleBMCLogServicesCollectionGet(
2455 crow::App& app, const crow::Request& req,
2456 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2457 const std::string& managerId)
2458 {
2459 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2460 {
2461 return;
2462 }
2463
2464 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2465 {
2466 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2467 return;
2468 }
2469
2470 // Collections don't include the static data added by SubRoute
2471 // because it has a duplicate entry for members
2472 asyncResp->res.jsonValue["@odata.type"] =
2473 "#LogServiceCollection.LogServiceCollection";
2474 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
2475 "/redfish/v1/Managers/{}/LogServices", BMCWEB_REDFISH_MANAGER_URI_NAME);
2476 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
2477 asyncResp->res.jsonValue["Description"] =
2478 "Collection of LogServices for this Manager";
2479 nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"];
2480 logServiceArray = nlohmann::json::array();
2481
2482 if constexpr (BMCWEB_REDFISH_BMC_JOURNAL)
2483 {
2484 nlohmann::json::object_t journal;
2485 journal["@odata.id"] =
2486 boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal",
2487 BMCWEB_REDFISH_MANAGER_URI_NAME);
2488 logServiceArray.emplace_back(std::move(journal));
2489 }
2490
2491 asyncResp->res.jsonValue["Members@odata.count"] = logServiceArray.size();
2492
2493 if constexpr (BMCWEB_REDFISH_DUMP_LOG)
2494 {
2495 constexpr std::array<std::string_view, 1> interfaces = {
2496 "xyz.openbmc_project.Collection.DeleteAll"};
2497 dbus::utility::getSubTreePaths(
2498 "/xyz/openbmc_project/dump", 0, interfaces,
2499 [asyncResp](const boost::system::error_code& ec,
2500 const dbus::utility::MapperGetSubTreePathsResponse&
2501 subTreePaths) {
2502 if (ec)
2503 {
2504 BMCWEB_LOG_ERROR(
2505 "handleBMCLogServicesCollectionGet respHandler got error {}",
2506 ec);
2507 // Assume that getting an error simply means there are no dump
2508 // LogServices. Return without adding any error response.
2509 return;
2510 }
2511
2512 nlohmann::json& logServiceArrayLocal =
2513 asyncResp->res.jsonValue["Members"];
2514
2515 for (const std::string& path : subTreePaths)
2516 {
2517 if (path == "/xyz/openbmc_project/dump/bmc")
2518 {
2519 nlohmann::json::object_t member;
2520 member["@odata.id"] = boost::urls::format(
2521 "/redfish/v1/Managers/{}/LogServices/Dump",
2522 BMCWEB_REDFISH_MANAGER_URI_NAME);
2523 logServiceArrayLocal.emplace_back(std::move(member));
2524 }
2525 else if (path == "/xyz/openbmc_project/dump/faultlog")
2526 {
2527 nlohmann::json::object_t member;
2528 member["@odata.id"] = boost::urls::format(
2529 "/redfish/v1/Managers/{}/LogServices/FaultLog",
2530 BMCWEB_REDFISH_MANAGER_URI_NAME);
2531 logServiceArrayLocal.emplace_back(std::move(member));
2532 }
2533 }
2534
2535 asyncResp->res.jsonValue["Members@odata.count"] =
2536 logServiceArrayLocal.size();
2537 });
2538 }
2539 }
2540
requestRoutesBMCLogServiceCollection(App & app)2541 inline void requestRoutesBMCLogServiceCollection(App& app)
2542 {
2543 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/")
2544 .privileges(redfish::privileges::getLogServiceCollection)
2545 .methods(boost::beast::http::verb::get)(
2546 std::bind_front(handleBMCLogServicesCollectionGet, std::ref(app)));
2547 }
2548
requestRoutesBMCJournalLogService(App & app)2549 inline void requestRoutesBMCJournalLogService(App& app)
2550 {
2551 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/")
2552 .privileges(redfish::privileges::getLogService)
2553 .methods(boost::beast::http::verb::get)(
2554 [&app](const crow::Request& req,
2555 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2556 const std::string& managerId) {
2557 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2558 {
2559 return;
2560 }
2561
2562 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2563 {
2564 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2565 return;
2566 }
2567
2568 asyncResp->res.jsonValue["@odata.type"] =
2569 "#LogService.v1_2_0.LogService";
2570 asyncResp->res.jsonValue["@odata.id"] =
2571 boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal",
2572 BMCWEB_REDFISH_MANAGER_URI_NAME);
2573 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
2574 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
2575 asyncResp->res.jsonValue["Id"] = "Journal";
2576 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
2577
2578 std::pair<std::string, std::string> redfishDateTimeOffset =
2579 redfish::time_utils::getDateTimeOffsetNow();
2580 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
2581 asyncResp->res.jsonValue["DateTimeLocalOffset"] =
2582 redfishDateTimeOffset.second;
2583
2584 asyncResp->res.jsonValue["Entries"]["@odata.id"] = boost::urls::format(
2585 "/redfish/v1/Managers/{}/LogServices/Journal/Entries",
2586 BMCWEB_REDFISH_MANAGER_URI_NAME);
2587 });
2588 }
2589
2590 static int
fillBMCJournalLogEntryJson(const std::string & bmcJournalLogEntryID,sd_journal * journal,nlohmann::json::object_t & bmcJournalLogEntryJson)2591 fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID,
2592 sd_journal* journal,
2593 nlohmann::json::object_t& bmcJournalLogEntryJson)
2594 {
2595 // Get the Log Entry contents
2596 int ret = 0;
2597
2598 std::string message;
2599 std::string_view syslogID;
2600 ret = getJournalMetadata(journal, "SYSLOG_IDENTIFIER", syslogID);
2601 if (ret < 0)
2602 {
2603 BMCWEB_LOG_DEBUG("Failed to read SYSLOG_IDENTIFIER field: {}",
2604 strerror(-ret));
2605 }
2606 if (!syslogID.empty())
2607 {
2608 message += std::string(syslogID) + ": ";
2609 }
2610
2611 std::string_view msg;
2612 ret = getJournalMetadata(journal, "MESSAGE", msg);
2613 if (ret < 0)
2614 {
2615 BMCWEB_LOG_ERROR("Failed to read MESSAGE field: {}", strerror(-ret));
2616 return 1;
2617 }
2618 message += std::string(msg);
2619
2620 // Get the severity from the PRIORITY field
2621 long int severity = 8; // Default to an invalid priority
2622 ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
2623 if (ret < 0)
2624 {
2625 BMCWEB_LOG_DEBUG("Failed to read PRIORITY field: {}", strerror(-ret));
2626 }
2627
2628 // Get the Created time from the timestamp
2629 std::string entryTimeStr;
2630 if (!getEntryTimestamp(journal, entryTimeStr))
2631 {
2632 return 1;
2633 }
2634
2635 // Fill in the log entry with the gathered data
2636 bmcJournalLogEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
2637 bmcJournalLogEntryJson["@odata.id"] = boost::urls::format(
2638 "/redfish/v1/Managers/{}/LogServices/Journal/Entries/{}",
2639 BMCWEB_REDFISH_MANAGER_URI_NAME, bmcJournalLogEntryID);
2640 bmcJournalLogEntryJson["Name"] = "BMC Journal Entry";
2641 bmcJournalLogEntryJson["Id"] = bmcJournalLogEntryID;
2642 bmcJournalLogEntryJson["Message"] = std::move(message);
2643 bmcJournalLogEntryJson["EntryType"] = "Oem";
2644 log_entry::EventSeverity severityEnum = log_entry::EventSeverity::OK;
2645 if (severity <= 2)
2646 {
2647 severityEnum = log_entry::EventSeverity::Critical;
2648 }
2649 else if (severity <= 4)
2650 {
2651 severityEnum = log_entry::EventSeverity::Warning;
2652 }
2653
2654 bmcJournalLogEntryJson["Severity"] = severityEnum;
2655 bmcJournalLogEntryJson["OemRecordFormat"] = "BMC Journal Entry";
2656 bmcJournalLogEntryJson["Created"] = std::move(entryTimeStr);
2657 return 0;
2658 }
2659
requestRoutesBMCJournalLogEntryCollection(App & app)2660 inline void requestRoutesBMCJournalLogEntryCollection(App& app)
2661 {
2662 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/")
2663 .privileges(redfish::privileges::getLogEntryCollection)
2664 .methods(boost::beast::http::verb::get)(
2665 [&app](const crow::Request& req,
2666 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2667 const std::string& managerId) {
2668 query_param::QueryCapabilities capabilities = {
2669 .canDelegateTop = true,
2670 .canDelegateSkip = true,
2671 };
2672 query_param::Query delegatedQuery;
2673 if (!redfish::setUpRedfishRouteWithDelegation(
2674 app, req, asyncResp, delegatedQuery, capabilities))
2675 {
2676 return;
2677 }
2678
2679 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2680 {
2681 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2682 return;
2683 }
2684
2685 size_t skip = delegatedQuery.skip.value_or(0);
2686 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
2687
2688 // Collections don't include the static data added by SubRoute
2689 // because it has a duplicate entry for members
2690 asyncResp->res.jsonValue["@odata.type"] =
2691 "#LogEntryCollection.LogEntryCollection";
2692 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
2693 "/redfish/v1/Managers/{}/LogServices/Journal/Entries",
2694 BMCWEB_REDFISH_MANAGER_URI_NAME);
2695 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
2696 asyncResp->res.jsonValue["Description"] =
2697 "Collection of BMC Journal Entries";
2698 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
2699 logEntryArray = nlohmann::json::array();
2700
2701 // Go through the journal and use the timestamp to create a
2702 // unique ID for each entry
2703 sd_journal* journalTmp = nullptr;
2704 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
2705 if (ret < 0)
2706 {
2707 BMCWEB_LOG_ERROR("failed to open journal: {}", strerror(-ret));
2708 messages::internalError(asyncResp->res);
2709 return;
2710 }
2711 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
2712 journalTmp, sd_journal_close);
2713 journalTmp = nullptr;
2714 uint64_t entryCount = 0;
2715 // Reset the unique ID on the first entry
2716 bool firstEntry = true;
2717 SD_JOURNAL_FOREACH(journal.get())
2718 {
2719 entryCount++;
2720 // Handle paging using skip (number of entries to skip from
2721 // the start) and top (number of entries to display)
2722 if (entryCount <= skip || entryCount > skip + top)
2723 {
2724 continue;
2725 }
2726
2727 std::string idStr;
2728 if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
2729 {
2730 continue;
2731 }
2732 firstEntry = false;
2733
2734 nlohmann::json::object_t bmcJournalLogEntry;
2735 if (fillBMCJournalLogEntryJson(idStr, journal.get(),
2736 bmcJournalLogEntry) != 0)
2737 {
2738 messages::internalError(asyncResp->res);
2739 return;
2740 }
2741 logEntryArray.emplace_back(std::move(bmcJournalLogEntry));
2742 }
2743 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
2744 if (skip + top < entryCount)
2745 {
2746 asyncResp->res
2747 .jsonValue["Members@odata.nextLink"] = boost::urls::format(
2748 "/redfish/v1/Managers/{}/LogServices/Journal/Entries?$skip={}",
2749 BMCWEB_REDFISH_MANAGER_URI_NAME, std::to_string(skip + top));
2750 }
2751 });
2752 }
2753
requestRoutesBMCJournalLogEntry(App & app)2754 inline void requestRoutesBMCJournalLogEntry(App& app)
2755 {
2756 BMCWEB_ROUTE(
2757 app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/<str>/")
2758 .privileges(redfish::privileges::getLogEntry)
2759 .methods(boost::beast::http::verb::get)(
2760 [&app](const crow::Request& req,
2761 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2762 const std::string& managerId, const std::string& entryID) {
2763 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2764 {
2765 return;
2766 }
2767
2768 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2769 {
2770 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2771 return;
2772 }
2773
2774 // Convert the unique ID back to a timestamp to find the entry
2775 sd_id128_t bootID{};
2776 uint64_t ts = 0;
2777 uint64_t index = 0;
2778 if (!getTimestampFromID(asyncResp, entryID, bootID, ts, index))
2779 {
2780 return;
2781 }
2782
2783 sd_journal* journalTmp = nullptr;
2784 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
2785 if (ret < 0)
2786 {
2787 BMCWEB_LOG_ERROR("failed to open journal: {}", strerror(-ret));
2788 messages::internalError(asyncResp->res);
2789 return;
2790 }
2791 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
2792 journalTmp, sd_journal_close);
2793 journalTmp = nullptr;
2794 // Go to the timestamp in the log and move to the entry at the
2795 // index tracking the unique ID
2796 std::string idStr;
2797 bool firstEntry = true;
2798 ret = sd_journal_seek_monotonic_usec(journal.get(), bootID, ts);
2799 if (ret < 0)
2800 {
2801 BMCWEB_LOG_ERROR("failed to seek to an entry in journal{}",
2802 strerror(-ret));
2803 messages::internalError(asyncResp->res);
2804 return;
2805 }
2806 for (uint64_t i = 0; i <= index; i++)
2807 {
2808 sd_journal_next(journal.get());
2809 if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
2810 {
2811 messages::internalError(asyncResp->res);
2812 return;
2813 }
2814 firstEntry = false;
2815 }
2816 // Confirm that the entry ID matches what was requested
2817 if (idStr != entryID)
2818 {
2819 messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
2820 return;
2821 }
2822
2823 nlohmann::json::object_t bmcJournalLogEntry;
2824 if (fillBMCJournalLogEntryJson(entryID, journal.get(),
2825 bmcJournalLogEntry) != 0)
2826 {
2827 messages::internalError(asyncResp->res);
2828 return;
2829 }
2830 asyncResp->res.jsonValue.update(bmcJournalLogEntry);
2831 });
2832 }
2833
2834 inline void
getDumpServiceInfo(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & dumpType)2835 getDumpServiceInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2836 const std::string& dumpType)
2837 {
2838 std::string dumpPath;
2839 std::string overWritePolicy;
2840 bool collectDiagnosticDataSupported = false;
2841
2842 if (dumpType == "BMC")
2843 {
2844 dumpPath = std::format("/redfish/v1/Managers/{}/LogServices/Dump",
2845 BMCWEB_REDFISH_MANAGER_URI_NAME);
2846 overWritePolicy = "WrapsWhenFull";
2847 collectDiagnosticDataSupported = true;
2848 }
2849 else if (dumpType == "FaultLog")
2850 {
2851 dumpPath = std::format("/redfish/v1/Managers/{}/LogServices/FaultLog",
2852 BMCWEB_REDFISH_MANAGER_URI_NAME);
2853 overWritePolicy = "Unknown";
2854 collectDiagnosticDataSupported = false;
2855 }
2856 else if (dumpType == "System")
2857 {
2858 dumpPath = std::format("/redfish/v1/Systems/{}/LogServices/Dump",
2859 BMCWEB_REDFISH_SYSTEM_URI_NAME);
2860 overWritePolicy = "WrapsWhenFull";
2861 collectDiagnosticDataSupported = true;
2862 }
2863 else
2864 {
2865 BMCWEB_LOG_ERROR("getDumpServiceInfo() invalid dump type: {}",
2866 dumpType);
2867 messages::internalError(asyncResp->res);
2868 return;
2869 }
2870
2871 asyncResp->res.jsonValue["@odata.id"] = dumpPath;
2872 asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
2873 asyncResp->res.jsonValue["Name"] = "Dump LogService";
2874 asyncResp->res.jsonValue["Description"] = dumpType + " Dump LogService";
2875 asyncResp->res.jsonValue["Id"] = std::filesystem::path(dumpPath).filename();
2876 asyncResp->res.jsonValue["OverWritePolicy"] = std::move(overWritePolicy);
2877
2878 std::pair<std::string, std::string> redfishDateTimeOffset =
2879 redfish::time_utils::getDateTimeOffsetNow();
2880 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
2881 asyncResp->res.jsonValue["DateTimeLocalOffset"] =
2882 redfishDateTimeOffset.second;
2883
2884 asyncResp->res.jsonValue["Entries"]["@odata.id"] = dumpPath + "/Entries";
2885
2886 if (collectDiagnosticDataSupported)
2887 {
2888 asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"]
2889 ["target"] =
2890 dumpPath + "/Actions/LogService.CollectDiagnosticData";
2891 }
2892
2893 constexpr std::array<std::string_view, 1> interfaces = {deleteAllInterface};
2894 dbus::utility::getSubTreePaths(
2895 "/xyz/openbmc_project/dump", 0, interfaces,
2896 [asyncResp, dumpType, dumpPath](
2897 const boost::system::error_code& ec,
2898 const dbus::utility::MapperGetSubTreePathsResponse& subTreePaths) {
2899 if (ec)
2900 {
2901 BMCWEB_LOG_ERROR("getDumpServiceInfo respHandler got error {}", ec);
2902 // Assume that getting an error simply means there are no dump
2903 // LogServices. Return without adding any error response.
2904 return;
2905 }
2906 std::string dbusDumpPath = getDumpPath(dumpType);
2907 for (const std::string& path : subTreePaths)
2908 {
2909 if (path == dbusDumpPath)
2910 {
2911 asyncResp->res
2912 .jsonValue["Actions"]["#LogService.ClearLog"]["target"] =
2913 dumpPath + "/Actions/LogService.ClearLog";
2914 break;
2915 }
2916 }
2917 });
2918 }
2919
handleLogServicesDumpServiceGet(crow::App & app,const std::string & dumpType,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)2920 inline void handleLogServicesDumpServiceGet(
2921 crow::App& app, const std::string& dumpType, const crow::Request& req,
2922 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2923 const std::string& managerId)
2924 {
2925 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2926 {
2927 return;
2928 }
2929
2930 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2931 {
2932 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2933 return;
2934 }
2935
2936 getDumpServiceInfo(asyncResp, dumpType);
2937 }
2938
handleLogServicesDumpServiceComputerSystemGet(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)2939 inline void handleLogServicesDumpServiceComputerSystemGet(
2940 crow::App& app, const crow::Request& req,
2941 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2942 const std::string& chassisId)
2943 {
2944 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2945 {
2946 return;
2947 }
2948 if (chassisId != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2949 {
2950 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
2951 return;
2952 }
2953 getDumpServiceInfo(asyncResp, "System");
2954 }
2955
handleLogServicesDumpEntriesCollectionGet(crow::App & app,const std::string & dumpType,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)2956 inline void handleLogServicesDumpEntriesCollectionGet(
2957 crow::App& app, const std::string& dumpType, const crow::Request& req,
2958 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2959 const std::string& managerId)
2960 {
2961 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2962 {
2963 return;
2964 }
2965
2966 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2967 {
2968 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2969 return;
2970 }
2971 getDumpEntryCollection(asyncResp, dumpType);
2972 }
2973
handleLogServicesDumpEntriesCollectionComputerSystemGet(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)2974 inline void handleLogServicesDumpEntriesCollectionComputerSystemGet(
2975 crow::App& app, const crow::Request& req,
2976 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2977 const std::string& chassisId)
2978 {
2979 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2980 {
2981 return;
2982 }
2983 if (chassisId != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2984 {
2985 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
2986 return;
2987 }
2988 getDumpEntryCollection(asyncResp, "System");
2989 }
2990
handleLogServicesDumpEntryGet(crow::App & app,const std::string & dumpType,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId,const std::string & dumpId)2991 inline void handleLogServicesDumpEntryGet(
2992 crow::App& app, const std::string& dumpType, const crow::Request& req,
2993 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2994 const std::string& managerId, const std::string& dumpId)
2995 {
2996 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2997 {
2998 return;
2999 }
3000 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
3001 {
3002 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
3003 return;
3004 }
3005 getDumpEntryById(asyncResp, dumpId, dumpType);
3006 }
3007
handleLogServicesDumpEntryComputerSystemGet(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & dumpId)3008 inline void handleLogServicesDumpEntryComputerSystemGet(
3009 crow::App& app, const crow::Request& req,
3010 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3011 const std::string& chassisId, const std::string& dumpId)
3012 {
3013 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3014 {
3015 return;
3016 }
3017 if (chassisId != BMCWEB_REDFISH_SYSTEM_URI_NAME)
3018 {
3019 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
3020 return;
3021 }
3022 getDumpEntryById(asyncResp, dumpId, "System");
3023 }
3024
handleLogServicesDumpEntryDelete(crow::App & app,const std::string & dumpType,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId,const std::string & dumpId)3025 inline void handleLogServicesDumpEntryDelete(
3026 crow::App& app, const std::string& dumpType, const crow::Request& req,
3027 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3028 const std::string& managerId, const std::string& dumpId)
3029 {
3030 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3031 {
3032 return;
3033 }
3034
3035 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
3036 {
3037 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
3038 return;
3039 }
3040 deleteDumpEntry(asyncResp, dumpId, dumpType);
3041 }
3042
handleLogServicesDumpEntryComputerSystemDelete(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & dumpId)3043 inline void handleLogServicesDumpEntryComputerSystemDelete(
3044 crow::App& app, const crow::Request& req,
3045 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3046 const std::string& chassisId, const std::string& dumpId)
3047 {
3048 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3049 {
3050 return;
3051 }
3052 if (chassisId != BMCWEB_REDFISH_SYSTEM_URI_NAME)
3053 {
3054 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
3055 return;
3056 }
3057 deleteDumpEntry(asyncResp, dumpId, "System");
3058 }
3059
handleLogServicesDumpEntryDownloadGet(crow::App & app,const std::string & dumpType,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId,const std::string & dumpId)3060 inline void handleLogServicesDumpEntryDownloadGet(
3061 crow::App& app, const std::string& dumpType, const crow::Request& req,
3062 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3063 const std::string& managerId, const std::string& dumpId)
3064 {
3065 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3066 {
3067 return;
3068 }
3069
3070 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
3071 {
3072 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
3073 return;
3074 }
3075 downloadDumpEntry(asyncResp, dumpId, dumpType);
3076 }
3077
handleDBusEventLogEntryDownloadGet(crow::App & app,const std::string & dumpType,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName,const std::string & entryID)3078 inline void handleDBusEventLogEntryDownloadGet(
3079 crow::App& app, const std::string& dumpType, const crow::Request& req,
3080 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3081 const std::string& systemName, const std::string& entryID)
3082 {
3083 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3084 {
3085 return;
3086 }
3087 if (!http_helpers::isContentTypeAllowed(
3088 req.getHeaderValue("Accept"),
3089 http_helpers::ContentType::OctetStream, true))
3090 {
3091 asyncResp->res.result(boost::beast::http::status::bad_request);
3092 return;
3093 }
3094 downloadEventLogEntry(asyncResp, systemName, entryID, dumpType);
3095 }
3096
handleLogServicesDumpCollectDiagnosticDataPost(crow::App & app,const std::string & dumpType,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)3097 inline void handleLogServicesDumpCollectDiagnosticDataPost(
3098 crow::App& app, const std::string& dumpType, const crow::Request& req,
3099 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3100 const std::string& managerId)
3101 {
3102 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3103 {
3104 return;
3105 }
3106 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
3107 {
3108 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
3109 return;
3110 }
3111
3112 createDump(asyncResp, req, dumpType);
3113 }
3114
handleLogServicesDumpCollectDiagnosticDataComputerSystemPost(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName)3115 inline void handleLogServicesDumpCollectDiagnosticDataComputerSystemPost(
3116 crow::App& app, const crow::Request& req,
3117 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3118 const std::string& systemName)
3119 {
3120 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3121 {
3122 return;
3123 }
3124
3125 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
3126 {
3127 // Option currently returns no systems. TBD
3128 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3129 systemName);
3130 return;
3131 }
3132 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
3133 {
3134 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3135 systemName);
3136 return;
3137 }
3138 createDump(asyncResp, req, "System");
3139 }
3140
handleLogServicesDumpClearLogPost(crow::App & app,const std::string & dumpType,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)3141 inline void handleLogServicesDumpClearLogPost(
3142 crow::App& app, const std::string& dumpType, const crow::Request& req,
3143 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3144 const std::string& managerId)
3145 {
3146 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3147 {
3148 return;
3149 }
3150
3151 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
3152 {
3153 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
3154 return;
3155 }
3156 clearDump(asyncResp, dumpType);
3157 }
3158
handleLogServicesDumpClearLogComputerSystemPost(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName)3159 inline void handleLogServicesDumpClearLogComputerSystemPost(
3160 crow::App& app, const crow::Request& req,
3161 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3162 const std::string& systemName)
3163 {
3164 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3165 {
3166 return;
3167 }
3168 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
3169 {
3170 // Option currently returns no systems. TBD
3171 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3172 systemName);
3173 return;
3174 }
3175 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
3176 {
3177 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3178 systemName);
3179 return;
3180 }
3181 clearDump(asyncResp, "System");
3182 }
3183
requestRoutesBMCDumpService(App & app)3184 inline void requestRoutesBMCDumpService(App& app)
3185 {
3186 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Dump/")
3187 .privileges(redfish::privileges::getLogService)
3188 .methods(boost::beast::http::verb::get)(std::bind_front(
3189 handleLogServicesDumpServiceGet, std::ref(app), "BMC"));
3190 }
3191
requestRoutesBMCDumpEntryCollection(App & app)3192 inline void requestRoutesBMCDumpEntryCollection(App& app)
3193 {
3194 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Dump/Entries/")
3195 .privileges(redfish::privileges::getLogEntryCollection)
3196 .methods(boost::beast::http::verb::get)(std::bind_front(
3197 handleLogServicesDumpEntriesCollectionGet, std::ref(app), "BMC"));
3198 }
3199
requestRoutesBMCDumpEntry(App & app)3200 inline void requestRoutesBMCDumpEntry(App& app)
3201 {
3202 BMCWEB_ROUTE(app,
3203 "/redfish/v1/Managers/<str>/LogServices/Dump/Entries/<str>/")
3204 .privileges(redfish::privileges::getLogEntry)
3205 .methods(boost::beast::http::verb::get)(std::bind_front(
3206 handleLogServicesDumpEntryGet, std::ref(app), "BMC"));
3207
3208 BMCWEB_ROUTE(app,
3209 "/redfish/v1/Managers/<str>/LogServices/Dump/Entries/<str>/")
3210 .privileges(redfish::privileges::deleteLogEntry)
3211 .methods(boost::beast::http::verb::delete_)(std::bind_front(
3212 handleLogServicesDumpEntryDelete, std::ref(app), "BMC"));
3213 }
3214
requestRoutesBMCDumpEntryDownload(App & app)3215 inline void requestRoutesBMCDumpEntryDownload(App& app)
3216 {
3217 BMCWEB_ROUTE(
3218 app,
3219 "/redfish/v1/Managers/<str>/LogServices/Dump/Entries/<str>/attachment/")
3220 .privileges(redfish::privileges::getLogEntry)
3221 .methods(boost::beast::http::verb::get)(std::bind_front(
3222 handleLogServicesDumpEntryDownloadGet, std::ref(app), "BMC"));
3223 }
3224
requestRoutesBMCDumpCreate(App & app)3225 inline void requestRoutesBMCDumpCreate(App& app)
3226 {
3227 BMCWEB_ROUTE(
3228 app,
3229 "/redfish/v1/Managers/<str>/LogServices/Dump/Actions/LogService.CollectDiagnosticData/")
3230 .privileges(redfish::privileges::postLogService)
3231 .methods(boost::beast::http::verb::post)(
3232 std::bind_front(handleLogServicesDumpCollectDiagnosticDataPost,
3233 std::ref(app), "BMC"));
3234 }
3235
requestRoutesBMCDumpClear(App & app)3236 inline void requestRoutesBMCDumpClear(App& app)
3237 {
3238 BMCWEB_ROUTE(
3239 app,
3240 "/redfish/v1/Managers/<str>/LogServices/Dump/Actions/LogService.ClearLog/")
3241 .privileges(redfish::privileges::postLogService)
3242 .methods(boost::beast::http::verb::post)(std::bind_front(
3243 handleLogServicesDumpClearLogPost, std::ref(app), "BMC"));
3244 }
3245
requestRoutesDBusEventLogEntryDownload(App & app)3246 inline void requestRoutesDBusEventLogEntryDownload(App& app)
3247 {
3248 BMCWEB_ROUTE(
3249 app,
3250 "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/attachment/")
3251 .privileges(redfish::privileges::getLogEntry)
3252 .methods(boost::beast::http::verb::get)(std::bind_front(
3253 handleDBusEventLogEntryDownloadGet, std::ref(app), "System"));
3254 }
3255
requestRoutesFaultLogDumpService(App & app)3256 inline void requestRoutesFaultLogDumpService(App& app)
3257 {
3258 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/FaultLog/")
3259 .privileges(redfish::privileges::getLogService)
3260 .methods(boost::beast::http::verb::get)(std::bind_front(
3261 handleLogServicesDumpServiceGet, std::ref(app), "FaultLog"));
3262 }
3263
requestRoutesFaultLogDumpEntryCollection(App & app)3264 inline void requestRoutesFaultLogDumpEntryCollection(App& app)
3265 {
3266 BMCWEB_ROUTE(app,
3267 "/redfish/v1/Managers/<str>/LogServices/FaultLog/Entries/")
3268 .privileges(redfish::privileges::getLogEntryCollection)
3269 .methods(boost::beast::http::verb::get)(
3270 std::bind_front(handleLogServicesDumpEntriesCollectionGet,
3271 std::ref(app), "FaultLog"));
3272 }
3273
requestRoutesFaultLogDumpEntry(App & app)3274 inline void requestRoutesFaultLogDumpEntry(App& app)
3275 {
3276 BMCWEB_ROUTE(
3277 app, "/redfish/v1/Managers/<str>/LogServices/FaultLog/Entries/<str>/")
3278 .privileges(redfish::privileges::getLogEntry)
3279 .methods(boost::beast::http::verb::get)(std::bind_front(
3280 handleLogServicesDumpEntryGet, std::ref(app), "FaultLog"));
3281
3282 BMCWEB_ROUTE(
3283 app, "/redfish/v1/Managers/<str>/LogServices/FaultLog/Entries/<str>/")
3284 .privileges(redfish::privileges::deleteLogEntry)
3285 .methods(boost::beast::http::verb::delete_)(std::bind_front(
3286 handleLogServicesDumpEntryDelete, std::ref(app), "FaultLog"));
3287 }
3288
requestRoutesFaultLogDumpClear(App & app)3289 inline void requestRoutesFaultLogDumpClear(App& app)
3290 {
3291 BMCWEB_ROUTE(
3292 app,
3293 "/redfish/v1/Managers/<str>/LogServices/FaultLog/Actions/LogService.ClearLog/")
3294 .privileges(redfish::privileges::postLogService)
3295 .methods(boost::beast::http::verb::post)(std::bind_front(
3296 handleLogServicesDumpClearLogPost, std::ref(app), "FaultLog"));
3297 }
3298
requestRoutesSystemDumpService(App & app)3299 inline void requestRoutesSystemDumpService(App& app)
3300 {
3301 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Dump/")
3302 .privileges(redfish::privileges::getLogService)
3303 .methods(boost::beast::http::verb::get)(std::bind_front(
3304 handleLogServicesDumpServiceComputerSystemGet, std::ref(app)));
3305 }
3306
requestRoutesSystemDumpEntryCollection(App & app)3307 inline void requestRoutesSystemDumpEntryCollection(App& app)
3308 {
3309 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/")
3310 .privileges(redfish::privileges::getLogEntryCollection)
3311 .methods(boost::beast::http::verb::get)(std::bind_front(
3312 handleLogServicesDumpEntriesCollectionComputerSystemGet,
3313 std::ref(app)));
3314 }
3315
requestRoutesSystemDumpEntry(App & app)3316 inline void requestRoutesSystemDumpEntry(App& app)
3317 {
3318 BMCWEB_ROUTE(app,
3319 "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/<str>/")
3320 .privileges(redfish::privileges::getLogEntry)
3321 .methods(boost::beast::http::verb::get)(std::bind_front(
3322 handleLogServicesDumpEntryComputerSystemGet, std::ref(app)));
3323
3324 BMCWEB_ROUTE(app,
3325 "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/<str>/")
3326 .privileges(redfish::privileges::deleteLogEntry)
3327 .methods(boost::beast::http::verb::delete_)(std::bind_front(
3328 handleLogServicesDumpEntryComputerSystemDelete, std::ref(app)));
3329 }
3330
requestRoutesSystemDumpCreate(App & app)3331 inline void requestRoutesSystemDumpCreate(App& app)
3332 {
3333 BMCWEB_ROUTE(
3334 app,
3335 "/redfish/v1/Systems/<str>/LogServices/Dump/Actions/LogService.CollectDiagnosticData/")
3336 .privileges(redfish::privileges::postLogService)
3337 .methods(boost::beast::http::verb::post)(std::bind_front(
3338 handleLogServicesDumpCollectDiagnosticDataComputerSystemPost,
3339 std::ref(app)));
3340 }
3341
requestRoutesSystemDumpClear(App & app)3342 inline void requestRoutesSystemDumpClear(App& app)
3343 {
3344 BMCWEB_ROUTE(
3345 app,
3346 "/redfish/v1/Systems/<str>/LogServices/Dump/Actions/LogService.ClearLog/")
3347 .privileges(redfish::privileges::postLogService)
3348 .methods(boost::beast::http::verb::post)(std::bind_front(
3349 handleLogServicesDumpClearLogComputerSystemPost, std::ref(app)));
3350 }
3351
requestRoutesCrashdumpService(App & app)3352 inline void requestRoutesCrashdumpService(App& app)
3353 {
3354 // Note: Deviated from redfish privilege registry for GET & HEAD
3355 // method for security reasons.
3356 /**
3357 * Functions triggers appropriate requests on DBus
3358 */
3359 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Crashdump/")
3360 // This is incorrect, should be:
3361 //.privileges(redfish::privileges::getLogService)
3362 .privileges({{"ConfigureManager"}})
3363 .methods(boost::beast::http::verb::get)(
3364 [&app](const crow::Request& req,
3365 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3366 const std::string& systemName) {
3367 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3368 {
3369 return;
3370 }
3371 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
3372 {
3373 // Option currently returns no systems. TBD
3374 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3375 systemName);
3376 return;
3377 }
3378 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
3379 {
3380 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3381 systemName);
3382 return;
3383 }
3384
3385 // Copy over the static data to include the entries added by
3386 // SubRoute
3387 asyncResp->res.jsonValue["@odata.id"] =
3388 std::format("/redfish/v1/Systems/{}/LogServices/Crashdump",
3389 BMCWEB_REDFISH_SYSTEM_URI_NAME);
3390 asyncResp->res.jsonValue["@odata.type"] =
3391 "#LogService.v1_2_0.LogService";
3392 asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service";
3393 asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service";
3394 asyncResp->res.jsonValue["Id"] = "Crashdump";
3395 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
3396 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
3397
3398 std::pair<std::string, std::string> redfishDateTimeOffset =
3399 redfish::time_utils::getDateTimeOffsetNow();
3400 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
3401 asyncResp->res.jsonValue["DateTimeLocalOffset"] =
3402 redfishDateTimeOffset.second;
3403
3404 asyncResp->res.jsonValue["Entries"]["@odata.id"] =
3405 std::format("/redfish/v1/Systems/{}/LogServices/Crashdump/Entries",
3406 BMCWEB_REDFISH_SYSTEM_URI_NAME);
3407 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]
3408 ["target"] = std::format(
3409 "/redfish/v1/Systems/{}/LogServices/Crashdump/Actions/LogService.ClearLog",
3410 BMCWEB_REDFISH_SYSTEM_URI_NAME);
3411 asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"]
3412 ["target"] = std::format(
3413 "/redfish/v1/Systems/{}/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData",
3414 BMCWEB_REDFISH_SYSTEM_URI_NAME);
3415 });
3416 }
3417
requestRoutesCrashdumpClear(App & app)3418 void inline requestRoutesCrashdumpClear(App& app)
3419 {
3420 BMCWEB_ROUTE(
3421 app,
3422 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Actions/LogService.ClearLog/")
3423 // This is incorrect, should be:
3424 //.privileges(redfish::privileges::postLogService)
3425 .privileges({{"ConfigureComponents"}})
3426 .methods(boost::beast::http::verb::post)(
3427 [&app](const crow::Request& req,
3428 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3429 const std::string& systemName) {
3430 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3431 {
3432 return;
3433 }
3434 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
3435 {
3436 // Option currently returns no systems. TBD
3437 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3438 systemName);
3439 return;
3440 }
3441 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
3442 {
3443 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3444 systemName);
3445 return;
3446 }
3447 crow::connections::systemBus->async_method_call(
3448 [asyncResp](const boost::system::error_code& ec,
3449 const std::string&) {
3450 if (ec)
3451 {
3452 messages::internalError(asyncResp->res);
3453 return;
3454 }
3455 messages::success(asyncResp->res);
3456 },
3457 crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll");
3458 });
3459 }
3460
3461 static void
logCrashdumpEntry(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & logID,nlohmann::json & logEntryJson)3462 logCrashdumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3463 const std::string& logID, nlohmann::json& logEntryJson)
3464 {
3465 auto getStoredLogCallback =
3466 [asyncResp, logID,
3467 &logEntryJson](const boost::system::error_code& ec,
3468 const dbus::utility::DBusPropertiesMap& params) {
3469 if (ec)
3470 {
3471 BMCWEB_LOG_DEBUG("failed to get log ec: {}", ec.message());
3472 if (ec.value() ==
3473 boost::system::linux_error::bad_request_descriptor)
3474 {
3475 messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
3476 }
3477 else
3478 {
3479 messages::internalError(asyncResp->res);
3480 }
3481 return;
3482 }
3483
3484 std::string timestamp{};
3485 std::string filename{};
3486 std::string logfile{};
3487 parseCrashdumpParameters(params, filename, timestamp, logfile);
3488
3489 if (filename.empty() || timestamp.empty())
3490 {
3491 messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
3492 return;
3493 }
3494
3495 std::string crashdumpURI =
3496 std::format("/redfish/v1/Systems/{}/LogServices/Crashdump/Entries/",
3497 BMCWEB_REDFISH_SYSTEM_URI_NAME) +
3498 logID + "/" + filename;
3499 nlohmann::json::object_t logEntry;
3500 logEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
3501 logEntry["@odata.id"] = boost::urls::format(
3502 "/redfish/v1/Systems/{}/LogServices/Crashdump/Entries/{}",
3503 BMCWEB_REDFISH_SYSTEM_URI_NAME, logID);
3504 logEntry["Name"] = "CPU Crashdump";
3505 logEntry["Id"] = logID;
3506 logEntry["EntryType"] = "Oem";
3507 logEntry["AdditionalDataURI"] = std::move(crashdumpURI);
3508 logEntry["DiagnosticDataType"] = "OEM";
3509 logEntry["OEMDiagnosticDataType"] = "PECICrashdump";
3510 logEntry["Created"] = std::move(timestamp);
3511
3512 // If logEntryJson references an array of LogEntry resources
3513 // ('Members' list), then push this as a new entry, otherwise set it
3514 // directly
3515 if (logEntryJson.is_array())
3516 {
3517 logEntryJson.push_back(logEntry);
3518 asyncResp->res.jsonValue["Members@odata.count"] =
3519 logEntryJson.size();
3520 }
3521 else
3522 {
3523 logEntryJson.update(logEntry);
3524 }
3525 };
3526 sdbusplus::asio::getAllProperties(
3527 *crow::connections::systemBus, crashdumpObject,
3528 crashdumpPath + std::string("/") + logID, crashdumpInterface,
3529 std::move(getStoredLogCallback));
3530 }
3531
requestRoutesCrashdumpEntryCollection(App & app)3532 inline void requestRoutesCrashdumpEntryCollection(App& app)
3533 {
3534 // Note: Deviated from redfish privilege registry for GET & HEAD
3535 // method for security reasons.
3536 /**
3537 * Functions triggers appropriate requests on DBus
3538 */
3539 BMCWEB_ROUTE(app,
3540 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/")
3541 // This is incorrect, should be.
3542 //.privileges(redfish::privileges::postLogEntryCollection)
3543 .privileges({{"ConfigureComponents"}})
3544 .methods(boost::beast::http::verb::get)(
3545 [&app](const crow::Request& req,
3546 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3547 const std::string& systemName) {
3548 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3549 {
3550 return;
3551 }
3552 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
3553 {
3554 // Option currently returns no systems. TBD
3555 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3556 systemName);
3557 return;
3558 }
3559 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
3560 {
3561 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3562 systemName);
3563 return;
3564 }
3565
3566 constexpr std::array<std::string_view, 1> interfaces = {
3567 crashdumpInterface};
3568 dbus::utility::getSubTreePaths(
3569 "/", 0, interfaces,
3570 [asyncResp](const boost::system::error_code& ec,
3571 const std::vector<std::string>& resp) {
3572 if (ec)
3573 {
3574 if (ec.value() !=
3575 boost::system::errc::no_such_file_or_directory)
3576 {
3577 BMCWEB_LOG_DEBUG("failed to get entries ec: {}",
3578 ec.message());
3579 messages::internalError(asyncResp->res);
3580 return;
3581 }
3582 }
3583 asyncResp->res.jsonValue["@odata.type"] =
3584 "#LogEntryCollection.LogEntryCollection";
3585 asyncResp->res.jsonValue["@odata.id"] = std::format(
3586 "/redfish/v1/Systems/{}/LogServices/Crashdump/Entries",
3587 BMCWEB_REDFISH_SYSTEM_URI_NAME);
3588 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries";
3589 asyncResp->res.jsonValue["Description"] =
3590 "Collection of Crashdump Entries";
3591 asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
3592 asyncResp->res.jsonValue["Members@odata.count"] = 0;
3593
3594 for (const std::string& path : resp)
3595 {
3596 const sdbusplus::message::object_path objPath(path);
3597 // Get the log ID
3598 std::string logID = objPath.filename();
3599 if (logID.empty())
3600 {
3601 continue;
3602 }
3603 // Add the log entry to the array
3604 logCrashdumpEntry(asyncResp, logID,
3605 asyncResp->res.jsonValue["Members"]);
3606 }
3607 });
3608 });
3609 }
3610
requestRoutesCrashdumpEntry(App & app)3611 inline void requestRoutesCrashdumpEntry(App& app)
3612 {
3613 // Note: Deviated from redfish privilege registry for GET & HEAD
3614 // method for security reasons.
3615
3616 BMCWEB_ROUTE(
3617 app, "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/<str>/")
3618 // this is incorrect, should be
3619 // .privileges(redfish::privileges::getLogEntry)
3620 .privileges({{"ConfigureComponents"}})
3621 .methods(boost::beast::http::verb::get)(
3622 [&app](const crow::Request& req,
3623 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3624 const std::string& systemName, const std::string& param) {
3625 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3626 {
3627 return;
3628 }
3629 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
3630 {
3631 // Option currently returns no systems. TBD
3632 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3633 systemName);
3634 return;
3635 }
3636 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
3637 {
3638 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3639 systemName);
3640 return;
3641 }
3642 const std::string& logID = param;
3643 logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue);
3644 });
3645 }
3646
requestRoutesCrashdumpFile(App & app)3647 inline void requestRoutesCrashdumpFile(App& app)
3648 {
3649 // Note: Deviated from redfish privilege registry for GET & HEAD
3650 // method for security reasons.
3651 BMCWEB_ROUTE(
3652 app,
3653 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/<str>/<str>/")
3654 .privileges(redfish::privileges::getLogEntry)
3655 .methods(boost::beast::http::verb::get)(
3656 [](const crow::Request& req,
3657 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3658 const std::string& systemName, const std::string& logID,
3659 const std::string& fileName) {
3660 // Do not call getRedfishRoute here since the crashdump file is not a
3661 // Redfish resource.
3662
3663 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
3664 {
3665 // Option currently returns no systems. TBD
3666 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3667 systemName);
3668 return;
3669 }
3670 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
3671 {
3672 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3673 systemName);
3674 return;
3675 }
3676
3677 auto getStoredLogCallback =
3678 [asyncResp, logID, fileName, url(boost::urls::url(req.url()))](
3679 const boost::system::error_code& ec,
3680 const std::vector<
3681 std::pair<std::string, dbus::utility::DbusVariantType>>&
3682 resp) {
3683 if (ec)
3684 {
3685 BMCWEB_LOG_DEBUG("failed to get log ec: {}", ec.message());
3686 messages::internalError(asyncResp->res);
3687 return;
3688 }
3689
3690 std::string dbusFilename{};
3691 std::string dbusTimestamp{};
3692 std::string dbusFilepath{};
3693
3694 parseCrashdumpParameters(resp, dbusFilename, dbusTimestamp,
3695 dbusFilepath);
3696
3697 if (dbusFilename.empty() || dbusTimestamp.empty() ||
3698 dbusFilepath.empty())
3699 {
3700 messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
3701 return;
3702 }
3703
3704 // Verify the file name parameter is correct
3705 if (fileName != dbusFilename)
3706 {
3707 messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
3708 return;
3709 }
3710
3711 if (!asyncResp->res.openFile(dbusFilepath))
3712 {
3713 messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
3714 return;
3715 }
3716
3717 // Configure this to be a file download when accessed
3718 // from a browser
3719 asyncResp->res.addHeader(
3720 boost::beast::http::field::content_disposition, "attachment");
3721 };
3722 sdbusplus::asio::getAllProperties(
3723 *crow::connections::systemBus, crashdumpObject,
3724 crashdumpPath + std::string("/") + logID, crashdumpInterface,
3725 std::move(getStoredLogCallback));
3726 });
3727 }
3728
3729 enum class OEMDiagnosticType
3730 {
3731 onDemand,
3732 telemetry,
3733 invalid,
3734 };
3735
getOEMDiagnosticType(std::string_view oemDiagStr)3736 inline OEMDiagnosticType getOEMDiagnosticType(std::string_view oemDiagStr)
3737 {
3738 if (oemDiagStr == "OnDemand")
3739 {
3740 return OEMDiagnosticType::onDemand;
3741 }
3742 if (oemDiagStr == "Telemetry")
3743 {
3744 return OEMDiagnosticType::telemetry;
3745 }
3746
3747 return OEMDiagnosticType::invalid;
3748 }
3749
requestRoutesCrashdumpCollect(App & app)3750 inline void requestRoutesCrashdumpCollect(App& app)
3751 {
3752 // Note: Deviated from redfish privilege registry for GET & HEAD
3753 // method for security reasons.
3754 BMCWEB_ROUTE(
3755 app,
3756 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData/")
3757 // The below is incorrect; Should be ConfigureManager
3758 //.privileges(redfish::privileges::postLogService)
3759 .privileges({{"ConfigureComponents"}})
3760 .methods(boost::beast::http::verb::post)(
3761 [&app](const crow::Request& req,
3762 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3763 const std::string& systemName) {
3764 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3765 {
3766 return;
3767 }
3768
3769 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
3770 {
3771 // Option currently returns no systems. TBD
3772 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3773 systemName);
3774 return;
3775 }
3776 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
3777 {
3778 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3779 systemName);
3780 return;
3781 }
3782
3783 std::string diagnosticDataType;
3784 std::string oemDiagnosticDataType;
3785 if (!redfish::json_util::readJsonAction(
3786 req, asyncResp->res, "DiagnosticDataType", diagnosticDataType,
3787 "OEMDiagnosticDataType", oemDiagnosticDataType))
3788 {
3789 return;
3790 }
3791
3792 if (diagnosticDataType != "OEM")
3793 {
3794 BMCWEB_LOG_ERROR(
3795 "Only OEM DiagnosticDataType supported for Crashdump");
3796 messages::actionParameterValueFormatError(
3797 asyncResp->res, diagnosticDataType, "DiagnosticDataType",
3798 "CollectDiagnosticData");
3799 return;
3800 }
3801
3802 OEMDiagnosticType oemDiagType =
3803 getOEMDiagnosticType(oemDiagnosticDataType);
3804
3805 std::string iface;
3806 std::string method;
3807 std::string taskMatchStr;
3808 if (oemDiagType == OEMDiagnosticType::onDemand)
3809 {
3810 iface = crashdumpOnDemandInterface;
3811 method = "GenerateOnDemandLog";
3812 taskMatchStr = "type='signal',"
3813 "interface='org.freedesktop.DBus.Properties',"
3814 "member='PropertiesChanged',"
3815 "arg0namespace='com.intel.crashdump'";
3816 }
3817 else if (oemDiagType == OEMDiagnosticType::telemetry)
3818 {
3819 iface = crashdumpTelemetryInterface;
3820 method = "GenerateTelemetryLog";
3821 taskMatchStr = "type='signal',"
3822 "interface='org.freedesktop.DBus.Properties',"
3823 "member='PropertiesChanged',"
3824 "arg0namespace='com.intel.crashdump'";
3825 }
3826 else
3827 {
3828 BMCWEB_LOG_ERROR("Unsupported OEMDiagnosticDataType: {}",
3829 oemDiagnosticDataType);
3830 messages::actionParameterValueFormatError(
3831 asyncResp->res, oemDiagnosticDataType, "OEMDiagnosticDataType",
3832 "CollectDiagnosticData");
3833 return;
3834 }
3835
3836 auto collectCrashdumpCallback =
3837 [asyncResp, payload(task::Payload(req)),
3838 taskMatchStr](const boost::system::error_code& ec,
3839 const std::string&) mutable {
3840 if (ec)
3841 {
3842 if (ec.value() == boost::system::errc::operation_not_supported)
3843 {
3844 messages::resourceInStandby(asyncResp->res);
3845 }
3846 else if (ec.value() ==
3847 boost::system::errc::device_or_resource_busy)
3848 {
3849 messages::serviceTemporarilyUnavailable(asyncResp->res,
3850 "60");
3851 }
3852 else
3853 {
3854 messages::internalError(asyncResp->res);
3855 }
3856 return;
3857 }
3858 std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
3859 [](const boost::system::error_code& ec2, sdbusplus::message_t&,
3860 const std::shared_ptr<task::TaskData>& taskData) {
3861 if (!ec2)
3862 {
3863 taskData->messages.emplace_back(messages::taskCompletedOK(
3864 std::to_string(taskData->index)));
3865 taskData->state = "Completed";
3866 }
3867 return task::completed;
3868 },
3869 taskMatchStr);
3870
3871 task->startTimer(std::chrono::minutes(5));
3872 task->populateResp(asyncResp->res);
3873 task->payload.emplace(std::move(payload));
3874 };
3875
3876 crow::connections::systemBus->async_method_call(
3877 std::move(collectCrashdumpCallback), crashdumpObject, crashdumpPath,
3878 iface, method);
3879 });
3880 }
3881
3882 /**
3883 * DBusLogServiceActionsClear class supports POST method for ClearLog action.
3884 */
requestRoutesDBusLogServiceActionsClear(App & app)3885 inline void requestRoutesDBusLogServiceActionsClear(App& app)
3886 {
3887 /**
3888 * Function handles POST method request.
3889 * The Clear Log actions does not require any parameter.The action deletes
3890 * all entries found in the Entries collection for this Log Service.
3891 */
3892
3893 BMCWEB_ROUTE(
3894 app,
3895 "/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/")
3896 .privileges(redfish::privileges::postLogService)
3897 .methods(boost::beast::http::verb::post)(
3898 [&app](const crow::Request& req,
3899 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3900 const std::string& systemName) {
3901 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3902 {
3903 return;
3904 }
3905 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
3906 {
3907 // Option currently returns no systems. TBD
3908 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3909 systemName);
3910 return;
3911 }
3912 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
3913 {
3914 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3915 systemName);
3916 return;
3917 }
3918 BMCWEB_LOG_DEBUG("Do delete all entries.");
3919
3920 // Process response from Logging service.
3921 auto respHandler = [asyncResp](const boost::system::error_code& ec) {
3922 BMCWEB_LOG_DEBUG("doClearLog resp_handler callback: Done");
3923 if (ec)
3924 {
3925 // TODO Handle for specific error code
3926 BMCWEB_LOG_ERROR("doClearLog resp_handler got error {}", ec);
3927 asyncResp->res.result(
3928 boost::beast::http::status::internal_server_error);
3929 return;
3930 }
3931
3932 asyncResp->res.result(boost::beast::http::status::no_content);
3933 };
3934
3935 // Make call to Logging service to request Clear Log
3936 crow::connections::systemBus->async_method_call(
3937 respHandler, "xyz.openbmc_project.Logging",
3938 "/xyz/openbmc_project/logging",
3939 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
3940 });
3941 }
3942
3943 /****************************************************
3944 * Redfish PostCode interfaces
3945 * using DBUS interface: getPostCodesTS
3946 ******************************************************/
requestRoutesPostCodesLogService(App & app)3947 inline void requestRoutesPostCodesLogService(App& app)
3948 {
3949 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/")
3950 .privileges(redfish::privileges::getLogService)
3951 .methods(boost::beast::http::verb::get)(
3952 [&app](const crow::Request& req,
3953 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3954 const std::string& systemName) {
3955 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3956 {
3957 return;
3958 }
3959 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
3960 {
3961 // Option currently returns no systems. TBD
3962 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3963 systemName);
3964 return;
3965 }
3966 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
3967 {
3968 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3969 systemName);
3970 return;
3971 }
3972 asyncResp->res.jsonValue["@odata.id"] =
3973 std::format("/redfish/v1/Systems/{}/LogServices/PostCodes",
3974 BMCWEB_REDFISH_SYSTEM_URI_NAME);
3975 asyncResp->res.jsonValue["@odata.type"] =
3976 "#LogService.v1_2_0.LogService";
3977 asyncResp->res.jsonValue["Name"] = "POST Code Log Service";
3978 asyncResp->res.jsonValue["Description"] = "POST Code Log Service";
3979 asyncResp->res.jsonValue["Id"] = "PostCodes";
3980 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
3981 asyncResp->res.jsonValue["Entries"]["@odata.id"] =
3982 std::format("/redfish/v1/Systems/{}/LogServices/PostCodes/Entries",
3983 BMCWEB_REDFISH_SYSTEM_URI_NAME);
3984
3985 std::pair<std::string, std::string> redfishDateTimeOffset =
3986 redfish::time_utils::getDateTimeOffsetNow();
3987 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
3988 asyncResp->res.jsonValue["DateTimeLocalOffset"] =
3989 redfishDateTimeOffset.second;
3990
3991 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]
3992 ["target"] = std::format(
3993 "/redfish/v1/Systems/{}/LogServices/PostCodes/Actions/LogService.ClearLog",
3994 BMCWEB_REDFISH_SYSTEM_URI_NAME);
3995 });
3996 }
3997
requestRoutesPostCodesClear(App & app)3998 inline void requestRoutesPostCodesClear(App& app)
3999 {
4000 BMCWEB_ROUTE(
4001 app,
4002 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Actions/LogService.ClearLog/")
4003 // The following privilege is incorrect; It should be ConfigureManager
4004 //.privileges(redfish::privileges::postLogService)
4005 .privileges({{"ConfigureComponents"}})
4006 .methods(boost::beast::http::verb::post)(
4007 [&app](const crow::Request& req,
4008 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
4009 const std::string& systemName) {
4010 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
4011 {
4012 return;
4013 }
4014 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
4015 {
4016 // Option currently returns no systems. TBD
4017 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
4018 systemName);
4019 return;
4020 }
4021 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
4022 {
4023 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
4024 systemName);
4025 return;
4026 }
4027 BMCWEB_LOG_DEBUG("Do delete all postcodes entries.");
4028
4029 // Make call to post-code service to request clear all
4030 crow::connections::systemBus->async_method_call(
4031 [asyncResp](const boost::system::error_code& ec) {
4032 if (ec)
4033 {
4034 // TODO Handle for specific error code
4035 BMCWEB_LOG_ERROR("doClearPostCodes resp_handler got error {}",
4036 ec);
4037 asyncResp->res.result(
4038 boost::beast::http::status::internal_server_error);
4039 messages::internalError(asyncResp->res);
4040 return;
4041 }
4042 messages::success(asyncResp->res);
4043 },
4044 "xyz.openbmc_project.State.Boot.PostCode0",
4045 "/xyz/openbmc_project/State/Boot/PostCode0",
4046 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
4047 });
4048 }
4049
4050 /**
4051 * @brief Parse post code ID and get the current value and index value
4052 * eg: postCodeID=B1-2, currentValue=1, index=2
4053 *
4054 * @param[in] postCodeID Post Code ID
4055 * @param[out] currentValue Current value
4056 * @param[out] index Index value
4057 *
4058 * @return bool true if the parsing is successful, false the parsing fails
4059 */
parsePostCode(std::string_view postCodeID,uint64_t & currentValue,uint16_t & index)4060 inline bool parsePostCode(std::string_view postCodeID, uint64_t& currentValue,
4061 uint16_t& index)
4062 {
4063 std::vector<std::string> split;
4064 bmcweb::split(split, postCodeID, '-');
4065 if (split.size() != 2)
4066 {
4067 return false;
4068 }
4069 std::string_view postCodeNumber = split[0];
4070 if (postCodeNumber.size() < 2)
4071 {
4072 return false;
4073 }
4074 if (postCodeNumber[0] != 'B')
4075 {
4076 return false;
4077 }
4078 postCodeNumber.remove_prefix(1);
4079 auto [ptrIndex, ecIndex] = std::from_chars(postCodeNumber.begin(),
4080 postCodeNumber.end(), index);
4081 if (ptrIndex != postCodeNumber.end() || ecIndex != std::errc())
4082 {
4083 return false;
4084 }
4085
4086 std::string_view postCodeIndex = split[1];
4087
4088 auto [ptrValue, ecValue] = std::from_chars(
4089 postCodeIndex.begin(), postCodeIndex.end(), currentValue);
4090
4091 return ptrValue == postCodeIndex.end() && ecValue == std::errc();
4092 }
4093
fillPostCodeEntry(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::container::flat_map<uint64_t,std::tuple<uint64_t,std::vector<uint8_t>>> & postcode,const uint16_t bootIndex,const uint64_t codeIndex=0,const uint64_t skip=0,const uint64_t top=0)4094 static bool fillPostCodeEntry(
4095 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
4096 const boost::container::flat_map<
4097 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& postcode,
4098 const uint16_t bootIndex, const uint64_t codeIndex = 0,
4099 const uint64_t skip = 0, const uint64_t top = 0)
4100 {
4101 // Get the Message from the MessageRegistry
4102 const registries::Message* message =
4103 registries::getMessage("OpenBMC.0.2.BIOSPOSTCode");
4104 if (message == nullptr)
4105 {
4106 BMCWEB_LOG_ERROR("Couldn't find known message?");
4107 return false;
4108 }
4109 uint64_t currentCodeIndex = 0;
4110 uint64_t firstCodeTimeUs = 0;
4111 for (const std::pair<uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
4112 code : postcode)
4113 {
4114 currentCodeIndex++;
4115 std::string postcodeEntryID =
4116 "B" + std::to_string(bootIndex) + "-" +
4117 std::to_string(currentCodeIndex); // 1 based index in EntryID string
4118
4119 uint64_t usecSinceEpoch = code.first;
4120 uint64_t usTimeOffset = 0;
4121
4122 if (1 == currentCodeIndex)
4123 { // already incremented
4124 firstCodeTimeUs = code.first;
4125 }
4126 else
4127 {
4128 usTimeOffset = code.first - firstCodeTimeUs;
4129 }
4130
4131 // skip if no specific codeIndex is specified and currentCodeIndex does
4132 // not fall between top and skip
4133 if ((codeIndex == 0) &&
4134 (currentCodeIndex <= skip || currentCodeIndex > top))
4135 {
4136 continue;
4137 }
4138
4139 // skip if a specific codeIndex is specified and does not match the
4140 // currentIndex
4141 if ((codeIndex > 0) && (currentCodeIndex != codeIndex))
4142 {
4143 // This is done for simplicity. 1st entry is needed to calculate
4144 // time offset. To improve efficiency, one can get to the entry
4145 // directly (possibly with flatmap's nth method)
4146 continue;
4147 }
4148
4149 // currentCodeIndex is within top and skip or equal to specified code
4150 // index
4151
4152 // Get the Created time from the timestamp
4153 std::string entryTimeStr;
4154 entryTimeStr = redfish::time_utils::getDateTimeUintUs(usecSinceEpoch);
4155
4156 // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex)
4157 std::ostringstream hexCode;
4158 hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex
4159 << std::get<0>(code.second);
4160 std::ostringstream timeOffsetStr;
4161 // Set Fixed -Point Notation
4162 timeOffsetStr << std::fixed;
4163 // Set precision to 4 digits
4164 timeOffsetStr << std::setprecision(4);
4165 // Add double to stream
4166 timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000;
4167
4168 std::string bootIndexStr = std::to_string(bootIndex);
4169 std::string timeOffsetString = timeOffsetStr.str();
4170 std::string hexCodeStr = hexCode.str();
4171
4172 std::array<std::string_view, 3> messageArgs = {
4173 bootIndexStr, timeOffsetString, hexCodeStr};
4174
4175 std::string msg =
4176 redfish::registries::fillMessageArgs(messageArgs, message->message);
4177 if (msg.empty())
4178 {
4179 messages::internalError(asyncResp->res);
4180 return false;
4181 }
4182
4183 // Get Severity template from message registry
4184 std::string severity;
4185 if (message != nullptr)
4186 {
4187 severity = message->messageSeverity;
4188 }
4189
4190 // Format entry
4191 nlohmann::json::object_t bmcLogEntry;
4192 bmcLogEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
4193 bmcLogEntry["@odata.id"] = boost::urls::format(
4194 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries/{}",
4195 BMCWEB_REDFISH_SYSTEM_URI_NAME, postcodeEntryID);
4196 bmcLogEntry["Name"] = "POST Code Log Entry";
4197 bmcLogEntry["Id"] = postcodeEntryID;
4198 bmcLogEntry["Message"] = std::move(msg);
4199 bmcLogEntry["MessageId"] = "OpenBMC.0.2.BIOSPOSTCode";
4200 bmcLogEntry["MessageArgs"] = messageArgs;
4201 bmcLogEntry["EntryType"] = "Event";
4202 bmcLogEntry["Severity"] = std::move(severity);
4203 bmcLogEntry["Created"] = entryTimeStr;
4204 if (!std::get<std::vector<uint8_t>>(code.second).empty())
4205 {
4206 bmcLogEntry["AdditionalDataURI"] =
4207 std::format(
4208 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries/",
4209 BMCWEB_REDFISH_SYSTEM_URI_NAME) +
4210 postcodeEntryID + "/attachment";
4211 }
4212
4213 // codeIndex is only specified when querying single entry, return only
4214 // that entry in this case
4215 if (codeIndex != 0)
4216 {
4217 asyncResp->res.jsonValue.update(bmcLogEntry);
4218 return true;
4219 }
4220
4221 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
4222 logEntryArray.emplace_back(std::move(bmcLogEntry));
4223 }
4224
4225 // Return value is always false when querying multiple entries
4226 return false;
4227 }
4228
4229 static void
getPostCodeForEntry(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & entryId)4230 getPostCodeForEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
4231 const std::string& entryId)
4232 {
4233 uint16_t bootIndex = 0;
4234 uint64_t codeIndex = 0;
4235 if (!parsePostCode(entryId, codeIndex, bootIndex))
4236 {
4237 // Requested ID was not found
4238 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId);
4239 return;
4240 }
4241
4242 if (bootIndex == 0 || codeIndex == 0)
4243 {
4244 // 0 is an invalid index
4245 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId);
4246 return;
4247 }
4248
4249 crow::connections::systemBus->async_method_call(
4250 [asyncResp, entryId, bootIndex,
4251 codeIndex](const boost::system::error_code& ec,
4252 const boost::container::flat_map<
4253 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
4254 postcode) {
4255 if (ec)
4256 {
4257 BMCWEB_LOG_DEBUG("DBUS POST CODE PostCode response error");
4258 messages::internalError(asyncResp->res);
4259 return;
4260 }
4261
4262 if (postcode.empty())
4263 {
4264 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId);
4265 return;
4266 }
4267
4268 if (!fillPostCodeEntry(asyncResp, postcode, bootIndex, codeIndex))
4269 {
4270 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId);
4271 return;
4272 }
4273 },
4274 "xyz.openbmc_project.State.Boot.PostCode0",
4275 "/xyz/openbmc_project/State/Boot/PostCode0",
4276 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp",
4277 bootIndex);
4278 }
4279
4280 static void
getPostCodeForBoot(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const uint16_t bootIndex,const uint16_t bootCount,const uint64_t entryCount,size_t skip,size_t top)4281 getPostCodeForBoot(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
4282 const uint16_t bootIndex, const uint16_t bootCount,
4283 const uint64_t entryCount, size_t skip, size_t top)
4284 {
4285 crow::connections::systemBus->async_method_call(
4286 [asyncResp, bootIndex, bootCount, entryCount, skip,
4287 top](const boost::system::error_code& ec,
4288 const boost::container::flat_map<
4289 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
4290 postcode) {
4291 if (ec)
4292 {
4293 BMCWEB_LOG_DEBUG("DBUS POST CODE PostCode response error");
4294 messages::internalError(asyncResp->res);
4295 return;
4296 }
4297
4298 uint64_t endCount = entryCount;
4299 if (!postcode.empty())
4300 {
4301 endCount = entryCount + postcode.size();
4302 if (skip < endCount && (top + skip) > entryCount)
4303 {
4304 uint64_t thisBootSkip = std::max(static_cast<uint64_t>(skip),
4305 entryCount) -
4306 entryCount;
4307 uint64_t thisBootTop =
4308 std::min(static_cast<uint64_t>(top + skip), endCount) -
4309 entryCount;
4310
4311 fillPostCodeEntry(asyncResp, postcode, bootIndex, 0,
4312 thisBootSkip, thisBootTop);
4313 }
4314 asyncResp->res.jsonValue["Members@odata.count"] = endCount;
4315 }
4316
4317 // continue to previous bootIndex
4318 if (bootIndex < bootCount)
4319 {
4320 getPostCodeForBoot(asyncResp, static_cast<uint16_t>(bootIndex + 1),
4321 bootCount, endCount, skip, top);
4322 }
4323 else if (skip + top < endCount)
4324 {
4325 asyncResp->res.jsonValue["Members@odata.nextLink"] =
4326 std::format(
4327 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries?$skip=",
4328 BMCWEB_REDFISH_SYSTEM_URI_NAME) +
4329 std::to_string(skip + top);
4330 }
4331 },
4332 "xyz.openbmc_project.State.Boot.PostCode0",
4333 "/xyz/openbmc_project/State/Boot/PostCode0",
4334 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp",
4335 bootIndex);
4336 }
4337
4338 static void
getCurrentBootNumber(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,size_t skip,size_t top)4339 getCurrentBootNumber(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
4340 size_t skip, size_t top)
4341 {
4342 uint64_t entryCount = 0;
4343 sdbusplus::asio::getProperty<uint16_t>(
4344 *crow::connections::systemBus,
4345 "xyz.openbmc_project.State.Boot.PostCode0",
4346 "/xyz/openbmc_project/State/Boot/PostCode0",
4347 "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount",
4348 [asyncResp, entryCount, skip, top](const boost::system::error_code& ec,
4349 const uint16_t bootCount) {
4350 if (ec)
4351 {
4352 BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
4353 messages::internalError(asyncResp->res);
4354 return;
4355 }
4356 getPostCodeForBoot(asyncResp, 1, bootCount, entryCount, skip, top);
4357 });
4358 }
4359
requestRoutesPostCodesEntryCollection(App & app)4360 inline void requestRoutesPostCodesEntryCollection(App& app)
4361 {
4362 BMCWEB_ROUTE(app,
4363 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/")
4364 .privileges(redfish::privileges::getLogEntryCollection)
4365 .methods(boost::beast::http::verb::get)(
4366 [&app](const crow::Request& req,
4367 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
4368 const std::string& systemName) {
4369 query_param::QueryCapabilities capabilities = {
4370 .canDelegateTop = true,
4371 .canDelegateSkip = true,
4372 };
4373 query_param::Query delegatedQuery;
4374 if (!redfish::setUpRedfishRouteWithDelegation(
4375 app, req, asyncResp, delegatedQuery, capabilities))
4376 {
4377 return;
4378 }
4379 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
4380 {
4381 // Option currently returns no systems. TBD
4382 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
4383 systemName);
4384 return;
4385 }
4386
4387 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
4388 {
4389 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
4390 systemName);
4391 return;
4392 }
4393 asyncResp->res.jsonValue["@odata.type"] =
4394 "#LogEntryCollection.LogEntryCollection";
4395 asyncResp->res.jsonValue["@odata.id"] =
4396 std::format("/redfish/v1/Systems/{}/LogServices/PostCodes/Entries",
4397 BMCWEB_REDFISH_SYSTEM_URI_NAME);
4398 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries";
4399 asyncResp->res.jsonValue["Description"] =
4400 "Collection of POST Code Log Entries";
4401 asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
4402 asyncResp->res.jsonValue["Members@odata.count"] = 0;
4403 size_t skip = delegatedQuery.skip.value_or(0);
4404 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
4405 getCurrentBootNumber(asyncResp, skip, top);
4406 });
4407 }
4408
requestRoutesPostCodesEntryAdditionalData(App & app)4409 inline void requestRoutesPostCodesEntryAdditionalData(App& app)
4410 {
4411 BMCWEB_ROUTE(
4412 app,
4413 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/attachment/")
4414 .privileges(redfish::privileges::getLogEntry)
4415 .methods(boost::beast::http::verb::get)(
4416 [&app](const crow::Request& req,
4417 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
4418 const std::string& systemName,
4419 const std::string& postCodeID) {
4420 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
4421 {
4422 return;
4423 }
4424 if (!http_helpers::isContentTypeAllowed(
4425 req.getHeaderValue("Accept"),
4426 http_helpers::ContentType::OctetStream, true))
4427 {
4428 asyncResp->res.result(boost::beast::http::status::bad_request);
4429 return;
4430 }
4431 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
4432 {
4433 // Option currently returns no systems. TBD
4434 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
4435 systemName);
4436 return;
4437 }
4438 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
4439 {
4440 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
4441 systemName);
4442 return;
4443 }
4444
4445 uint64_t currentValue = 0;
4446 uint16_t index = 0;
4447 if (!parsePostCode(postCodeID, currentValue, index))
4448 {
4449 messages::resourceNotFound(asyncResp->res, "LogEntry", postCodeID);
4450 return;
4451 }
4452
4453 crow::connections::systemBus->async_method_call(
4454 [asyncResp, postCodeID, currentValue](
4455 const boost::system::error_code& ec,
4456 const std::vector<std::tuple<uint64_t, std::vector<uint8_t>>>&
4457 postcodes) {
4458 if (ec.value() == EBADR)
4459 {
4460 messages::resourceNotFound(asyncResp->res, "LogEntry",
4461 postCodeID);
4462 return;
4463 }
4464 if (ec)
4465 {
4466 BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
4467 messages::internalError(asyncResp->res);
4468 return;
4469 }
4470
4471 size_t value = static_cast<size_t>(currentValue) - 1;
4472 if (value == std::string::npos || postcodes.size() < currentValue)
4473 {
4474 BMCWEB_LOG_WARNING("Wrong currentValue value");
4475 messages::resourceNotFound(asyncResp->res, "LogEntry",
4476 postCodeID);
4477 return;
4478 }
4479
4480 const auto& [tID, c] = postcodes[value];
4481 if (c.empty())
4482 {
4483 BMCWEB_LOG_WARNING("No found post code data");
4484 messages::resourceNotFound(asyncResp->res, "LogEntry",
4485 postCodeID);
4486 return;
4487 }
4488 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
4489 const char* d = reinterpret_cast<const char*>(c.data());
4490 std::string_view strData(d, c.size());
4491
4492 asyncResp->res.addHeader(boost::beast::http::field::content_type,
4493 "application/octet-stream");
4494 asyncResp->res.addHeader(
4495 boost::beast::http::field::content_transfer_encoding, "Base64");
4496 asyncResp->res.write(crow::utility::base64encode(strData));
4497 },
4498 "xyz.openbmc_project.State.Boot.PostCode0",
4499 "/xyz/openbmc_project/State/Boot/PostCode0",
4500 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodes", index);
4501 });
4502 }
4503
requestRoutesPostCodesEntry(App & app)4504 inline void requestRoutesPostCodesEntry(App& app)
4505 {
4506 BMCWEB_ROUTE(
4507 app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/")
4508 .privileges(redfish::privileges::getLogEntry)
4509 .methods(boost::beast::http::verb::get)(
4510 [&app](const crow::Request& req,
4511 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
4512 const std::string& systemName, const std::string& targetID) {
4513 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
4514 {
4515 return;
4516 }
4517 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
4518 {
4519 // Option currently returns no systems. TBD
4520 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
4521 systemName);
4522 return;
4523 }
4524 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
4525 {
4526 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
4527 systemName);
4528 return;
4529 }
4530
4531 getPostCodeForEntry(asyncResp, targetID);
4532 });
4533 }
4534
4535 } // namespace redfish
4536