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 "generated/enums/log_service.hpp"
23 #include "gzfile.hpp"
24 #include "http_utility.hpp"
25 #include "human_sort.hpp"
26 #include "query.hpp"
27 #include "registries.hpp"
28 #include "registries/base_message_registry.hpp"
29 #include "registries/openbmc_message_registry.hpp"
30 #include "registries/privilege_registry.hpp"
31 #include "task.hpp"
32 #include "task_messages.hpp"
33 #include "utils/dbus_event_log_entry.hpp"
34 #include "utils/dbus_utils.hpp"
35 #include "utils/json_utils.hpp"
36 #include "utils/time_utils.hpp"
37
38 #include <systemd/sd-id128.h>
39 #include <tinyxml2.h>
40 #include <unistd.h>
41
42 #include <boost/beast/http/verb.hpp>
43 #include <boost/container/flat_map.hpp>
44 #include <boost/system/linux_error.hpp>
45 #include <boost/url/format.hpp>
46 #include <sdbusplus/asio/property.hpp>
47 #include <sdbusplus/unpack_properties.hpp>
48
49 #include <array>
50 #include <charconv>
51 #include <cstddef>
52 #include <filesystem>
53 #include <iterator>
54 #include <optional>
55 #include <ranges>
56 #include <span>
57 #include <string>
58 #include <string_view>
59 #include <variant>
60
61 namespace redfish
62 {
63
64 constexpr const char* crashdumpObject = "com.intel.crashdump";
65 constexpr const char* crashdumpPath = "/com/intel/crashdump";
66 constexpr const char* crashdumpInterface = "com.intel.crashdump";
67 constexpr const char* deleteAllInterface =
68 "xyz.openbmc_project.Collection.DeleteAll";
69 constexpr const char* crashdumpOnDemandInterface =
70 "com.intel.crashdump.OnDemand";
71 constexpr const char* crashdumpTelemetryInterface =
72 "com.intel.crashdump.Telemetry";
73
74 enum class DumpCreationProgress
75 {
76 DUMP_CREATE_SUCCESS,
77 DUMP_CREATE_FAILED,
78 DUMP_CREATE_INPROGRESS
79 };
80
81 namespace fs = std::filesystem;
82
translateSeverityDbusToRedfish(const std::string & s)83 inline std::string translateSeverityDbusToRedfish(const std::string& s)
84 {
85 if ((s == "xyz.openbmc_project.Logging.Entry.Level.Alert") ||
86 (s == "xyz.openbmc_project.Logging.Entry.Level.Critical") ||
87 (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") ||
88 (s == "xyz.openbmc_project.Logging.Entry.Level.Error"))
89 {
90 return "Critical";
91 }
92 if ((s == "xyz.openbmc_project.Logging.Entry.Level.Debug") ||
93 (s == "xyz.openbmc_project.Logging.Entry.Level.Informational") ||
94 (s == "xyz.openbmc_project.Logging.Entry.Level.Notice"))
95 {
96 return "OK";
97 }
98 if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
99 {
100 return "Warning";
101 }
102 return "";
103 }
104
getProviderNotifyAction(const std::string & notify)105 inline std::optional<bool> getProviderNotifyAction(const std::string& notify)
106 {
107 std::optional<bool> notifyAction;
108 if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Notify")
109 {
110 notifyAction = true;
111 }
112 else if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Inhibit")
113 {
114 notifyAction = false;
115 }
116
117 return notifyAction;
118 }
119
getDumpPath(std::string_view dumpType)120 inline std::string getDumpPath(std::string_view dumpType)
121 {
122 std::string dbusDumpPath = "/xyz/openbmc_project/dump/";
123 std::ranges::transform(dumpType, std::back_inserter(dbusDumpPath),
124 bmcweb::asciiToLower);
125
126 return dbusDumpPath;
127 }
128
getUniqueEntryID(const std::string & logEntry,std::string & entryID,const bool firstEntry=true)129 inline bool getUniqueEntryID(const std::string& logEntry, std::string& entryID,
130 const bool firstEntry = true)
131 {
132 static time_t prevTs = 0;
133 static int index = 0;
134 if (firstEntry)
135 {
136 prevTs = 0;
137 }
138
139 // Get the entry timestamp
140 std::time_t curTs = 0;
141 std::tm timeStruct = {};
142 std::istringstream entryStream(logEntry);
143 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
144 {
145 curTs = std::mktime(&timeStruct);
146 }
147 // If the timestamp isn't unique, increment the index
148 if (curTs == prevTs)
149 {
150 index++;
151 }
152 else
153 {
154 // Otherwise, reset it
155 index = 0;
156 }
157 // Save the timestamp
158 prevTs = curTs;
159
160 entryID = std::to_string(curTs);
161 if (index > 0)
162 {
163 entryID += "_" + std::to_string(index);
164 }
165 return true;
166 }
167
168 inline bool
getRedfishLogFiles(std::vector<std::filesystem::path> & redfishLogFiles)169 getRedfishLogFiles(std::vector<std::filesystem::path>& redfishLogFiles)
170 {
171 static const std::filesystem::path redfishLogDir = "/var/log";
172 static const std::string redfishLogFilename = "redfish";
173
174 // Loop through the directory looking for redfish log files
175 for (const std::filesystem::directory_entry& dirEnt :
176 std::filesystem::directory_iterator(redfishLogDir))
177 {
178 // If we find a redfish log file, save the path
179 std::string filename = dirEnt.path().filename();
180 if (filename.starts_with(redfishLogFilename))
181 {
182 redfishLogFiles.emplace_back(redfishLogDir / filename);
183 }
184 }
185 // As the log files rotate, they are appended with a ".#" that is higher for
186 // the older logs. Since we don't expect more than 10 log files, we
187 // can just sort the list to get them in order from newest to oldest
188 std::ranges::sort(redfishLogFiles);
189
190 return !redfishLogFiles.empty();
191 }
192
193 inline log_entry::OriginatorTypes
mapDbusOriginatorTypeToRedfish(const std::string & originatorType)194 mapDbusOriginatorTypeToRedfish(const std::string& originatorType)
195 {
196 if (originatorType ==
197 "xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.Client")
198 {
199 return log_entry::OriginatorTypes::Client;
200 }
201 if (originatorType ==
202 "xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.Internal")
203 {
204 return log_entry::OriginatorTypes::Internal;
205 }
206 if (originatorType ==
207 "xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.SupportingService")
208 {
209 return log_entry::OriginatorTypes::SupportingService;
210 }
211 return log_entry::OriginatorTypes::Invalid;
212 }
213
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)214 inline void parseDumpEntryFromDbusObject(
215 const dbus::utility::ManagedObjectType::value_type& object,
216 std::string& dumpStatus, uint64_t& size, uint64_t& timestampUs,
217 std::string& originatorId, log_entry::OriginatorTypes& originatorType,
218 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
219 {
220 for (const auto& interfaceMap : object.second)
221 {
222 if (interfaceMap.first == "xyz.openbmc_project.Common.Progress")
223 {
224 for (const auto& propertyMap : interfaceMap.second)
225 {
226 if (propertyMap.first == "Status")
227 {
228 const auto* status =
229 std::get_if<std::string>(&propertyMap.second);
230 if (status == nullptr)
231 {
232 messages::internalError(asyncResp->res);
233 break;
234 }
235 dumpStatus = *status;
236 }
237 }
238 }
239 else if (interfaceMap.first == "xyz.openbmc_project.Dump.Entry")
240 {
241 for (const auto& propertyMap : interfaceMap.second)
242 {
243 if (propertyMap.first == "Size")
244 {
245 const auto* sizePtr =
246 std::get_if<uint64_t>(&propertyMap.second);
247 if (sizePtr == nullptr)
248 {
249 messages::internalError(asyncResp->res);
250 break;
251 }
252 size = *sizePtr;
253 break;
254 }
255 }
256 }
257 else if (interfaceMap.first == "xyz.openbmc_project.Time.EpochTime")
258 {
259 for (const auto& propertyMap : interfaceMap.second)
260 {
261 if (propertyMap.first == "Elapsed")
262 {
263 const uint64_t* usecsTimeStamp =
264 std::get_if<uint64_t>(&propertyMap.second);
265 if (usecsTimeStamp == nullptr)
266 {
267 messages::internalError(asyncResp->res);
268 break;
269 }
270 timestampUs = *usecsTimeStamp;
271 break;
272 }
273 }
274 }
275 else if (interfaceMap.first ==
276 "xyz.openbmc_project.Common.OriginatedBy")
277 {
278 for (const auto& propertyMap : interfaceMap.second)
279 {
280 if (propertyMap.first == "OriginatorId")
281 {
282 const std::string* id =
283 std::get_if<std::string>(&propertyMap.second);
284 if (id == nullptr)
285 {
286 messages::internalError(asyncResp->res);
287 break;
288 }
289 originatorId = *id;
290 }
291
292 if (propertyMap.first == "OriginatorType")
293 {
294 const std::string* type =
295 std::get_if<std::string>(&propertyMap.second);
296 if (type == nullptr)
297 {
298 messages::internalError(asyncResp->res);
299 break;
300 }
301
302 originatorType = mapDbusOriginatorTypeToRedfish(*type);
303 if (originatorType == log_entry::OriginatorTypes::Invalid)
304 {
305 messages::internalError(asyncResp->res);
306 break;
307 }
308 }
309 }
310 }
311 }
312 }
313
getDumpEntriesPath(const std::string & dumpType)314 static std::string getDumpEntriesPath(const std::string& dumpType)
315 {
316 std::string entriesPath;
317
318 if (dumpType == "BMC")
319 {
320 entriesPath =
321 std::format("/redfish/v1/Managers/{}/LogServices/Dump/Entries/",
322 BMCWEB_REDFISH_MANAGER_URI_NAME);
323 }
324 else if (dumpType == "FaultLog")
325 {
326 entriesPath =
327 std::format("/redfish/v1/Managers/{}/LogServices/FaultLog/Entries/",
328 BMCWEB_REDFISH_MANAGER_URI_NAME);
329 }
330 else if (dumpType == "System")
331 {
332 entriesPath =
333 std::format("/redfish/v1/Systems/{}/LogServices/Dump/Entries/",
334 BMCWEB_REDFISH_SYSTEM_URI_NAME);
335 }
336 else
337 {
338 BMCWEB_LOG_ERROR("getDumpEntriesPath() invalid dump type: {}",
339 dumpType);
340 }
341
342 // Returns empty string on error
343 return entriesPath;
344 }
345
346 inline void
getDumpEntryCollection(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & dumpType)347 getDumpEntryCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
348 const std::string& dumpType)
349 {
350 std::string entriesPath = getDumpEntriesPath(dumpType);
351 if (entriesPath.empty())
352 {
353 messages::internalError(asyncResp->res);
354 return;
355 }
356
357 sdbusplus::message::object_path path("/xyz/openbmc_project/dump");
358 dbus::utility::getManagedObjects(
359 "xyz.openbmc_project.Dump.Manager", path,
360 [asyncResp, entriesPath,
361 dumpType](const boost::system::error_code& ec,
362 const dbus::utility::ManagedObjectType& objects) {
363 if (ec)
364 {
365 BMCWEB_LOG_ERROR("DumpEntry resp_handler got error {}", ec);
366 messages::internalError(asyncResp->res);
367 return;
368 }
369
370 // Remove ending slash
371 std::string odataIdStr = entriesPath;
372 if (!odataIdStr.empty())
373 {
374 odataIdStr.pop_back();
375 }
376
377 asyncResp->res.jsonValue["@odata.type"] =
378 "#LogEntryCollection.LogEntryCollection";
379 asyncResp->res.jsonValue["@odata.id"] = std::move(odataIdStr);
380 asyncResp->res.jsonValue["Name"] = dumpType + " Dump Entries";
381 asyncResp->res.jsonValue["Description"] =
382 "Collection of " + dumpType + " Dump Entries";
383
384 nlohmann::json::array_t entriesArray;
385 std::string dumpEntryPath = getDumpPath(dumpType) + "/entry/";
386
387 dbus::utility::ManagedObjectType resp(objects);
388 std::ranges::sort(resp, [](const auto& l, const auto& r) {
389 return AlphanumLess<std::string>()(l.first.filename(),
390 r.first.filename());
391 });
392
393 for (auto& object : resp)
394 {
395 if (object.first.str.find(dumpEntryPath) == std::string::npos)
396 {
397 continue;
398 }
399 uint64_t timestampUs = 0;
400 uint64_t size = 0;
401 std::string dumpStatus;
402 std::string originatorId;
403 log_entry::OriginatorTypes originatorType =
404 log_entry::OriginatorTypes::Internal;
405 nlohmann::json::object_t thisEntry;
406
407 std::string entryID = object.first.filename();
408 if (entryID.empty())
409 {
410 continue;
411 }
412
413 parseDumpEntryFromDbusObject(object, dumpStatus, size,
414 timestampUs, originatorId,
415 originatorType, asyncResp);
416
417 if (dumpStatus !=
418 "xyz.openbmc_project.Common.Progress.OperationStatus.Completed" &&
419 !dumpStatus.empty())
420 {
421 // Dump status is not Complete, no need to enumerate
422 continue;
423 }
424
425 thisEntry["@odata.type"] = "#LogEntry.v1_11_0.LogEntry";
426 thisEntry["@odata.id"] = entriesPath + entryID;
427 thisEntry["Id"] = entryID;
428 thisEntry["EntryType"] = "Event";
429 thisEntry["Name"] = dumpType + " Dump Entry";
430 thisEntry["Created"] =
431 redfish::time_utils::getDateTimeUintUs(timestampUs);
432
433 if (!originatorId.empty())
434 {
435 thisEntry["Originator"] = originatorId;
436 thisEntry["OriginatorType"] = originatorType;
437 }
438
439 if (dumpType == "BMC")
440 {
441 thisEntry["DiagnosticDataType"] = "Manager";
442 thisEntry["AdditionalDataURI"] =
443 entriesPath + entryID + "/attachment";
444 thisEntry["AdditionalDataSizeBytes"] = size;
445 }
446 else if (dumpType == "System")
447 {
448 thisEntry["DiagnosticDataType"] = "OEM";
449 thisEntry["OEMDiagnosticDataType"] = "System";
450 thisEntry["AdditionalDataURI"] =
451 entriesPath + entryID + "/attachment";
452 thisEntry["AdditionalDataSizeBytes"] = size;
453 }
454 entriesArray.emplace_back(std::move(thisEntry));
455 }
456 asyncResp->res.jsonValue["Members@odata.count"] =
457 entriesArray.size();
458 asyncResp->res.jsonValue["Members"] = std::move(entriesArray);
459 });
460 }
461
462 inline void
getDumpEntryById(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & entryID,const std::string & dumpType)463 getDumpEntryById(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
464 const std::string& entryID, const std::string& dumpType)
465 {
466 std::string entriesPath = getDumpEntriesPath(dumpType);
467 if (entriesPath.empty())
468 {
469 messages::internalError(asyncResp->res);
470 return;
471 }
472
473 sdbusplus::message::object_path path("/xyz/openbmc_project/dump");
474 dbus::utility::getManagedObjects(
475 "xyz.openbmc_project.Dump.Manager", path,
476 [asyncResp, entryID, dumpType,
477 entriesPath](const boost::system::error_code& ec,
478 const dbus::utility::ManagedObjectType& resp) {
479 if (ec)
480 {
481 BMCWEB_LOG_ERROR("DumpEntry resp_handler got error {}", ec);
482 messages::internalError(asyncResp->res);
483 return;
484 }
485
486 bool foundDumpEntry = false;
487 std::string dumpEntryPath = getDumpPath(dumpType) + "/entry/";
488
489 for (const auto& objectPath : resp)
490 {
491 if (objectPath.first.str != dumpEntryPath + entryID)
492 {
493 continue;
494 }
495
496 foundDumpEntry = true;
497 uint64_t timestampUs = 0;
498 uint64_t size = 0;
499 std::string dumpStatus;
500 std::string originatorId;
501 log_entry::OriginatorTypes originatorType =
502 log_entry::OriginatorTypes::Internal;
503
504 parseDumpEntryFromDbusObject(objectPath, dumpStatus, size,
505 timestampUs, originatorId,
506 originatorType, asyncResp);
507
508 if (dumpStatus !=
509 "xyz.openbmc_project.Common.Progress.OperationStatus.Completed" &&
510 !dumpStatus.empty())
511 {
512 // Dump status is not Complete
513 // return not found until status is changed to Completed
514 messages::resourceNotFound(asyncResp->res,
515 dumpType + " dump", entryID);
516 return;
517 }
518
519 asyncResp->res.jsonValue["@odata.type"] =
520 "#LogEntry.v1_11_0.LogEntry";
521 asyncResp->res.jsonValue["@odata.id"] = entriesPath + entryID;
522 asyncResp->res.jsonValue["Id"] = entryID;
523 asyncResp->res.jsonValue["EntryType"] = "Event";
524 asyncResp->res.jsonValue["Name"] = dumpType + " Dump Entry";
525 asyncResp->res.jsonValue["Created"] =
526 redfish::time_utils::getDateTimeUintUs(timestampUs);
527
528 if (!originatorId.empty())
529 {
530 asyncResp->res.jsonValue["Originator"] = originatorId;
531 asyncResp->res.jsonValue["OriginatorType"] = originatorType;
532 }
533
534 if (dumpType == "BMC")
535 {
536 asyncResp->res.jsonValue["DiagnosticDataType"] = "Manager";
537 asyncResp->res.jsonValue["AdditionalDataURI"] =
538 entriesPath + entryID + "/attachment";
539 asyncResp->res.jsonValue["AdditionalDataSizeBytes"] = size;
540 }
541 else if (dumpType == "System")
542 {
543 asyncResp->res.jsonValue["DiagnosticDataType"] = "OEM";
544 asyncResp->res.jsonValue["OEMDiagnosticDataType"] =
545 "System";
546 asyncResp->res.jsonValue["AdditionalDataURI"] =
547 entriesPath + entryID + "/attachment";
548 asyncResp->res.jsonValue["AdditionalDataSizeBytes"] = size;
549 }
550 }
551 if (!foundDumpEntry)
552 {
553 BMCWEB_LOG_WARNING("Can't find Dump Entry {}", entryID);
554 messages::resourceNotFound(asyncResp->res, dumpType + " dump",
555 entryID);
556 return;
557 }
558 });
559 }
560
deleteDumpEntry(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & entryID,const std::string & dumpType)561 inline void deleteDumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
562 const std::string& entryID,
563 const std::string& dumpType)
564 {
565 auto respHandler = [asyncResp,
566 entryID](const boost::system::error_code& ec) {
567 BMCWEB_LOG_DEBUG("Dump Entry doDelete callback: Done");
568 if (ec)
569 {
570 if (ec.value() == EBADR)
571 {
572 messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
573 return;
574 }
575 BMCWEB_LOG_ERROR(
576 "Dump (DBus) doDelete respHandler got error {} entryID={}", ec,
577 entryID);
578 messages::internalError(asyncResp->res);
579 return;
580 }
581 };
582
583 crow::connections::systemBus->async_method_call(
584 respHandler, "xyz.openbmc_project.Dump.Manager",
585 std::format("{}/entry/{}", getDumpPath(dumpType), entryID),
586 "xyz.openbmc_project.Object.Delete", "Delete");
587 }
checkSizeLimit(int fd,crow::Response & res)588 inline bool checkSizeLimit(int fd, crow::Response& res)
589 {
590 long long int size = lseek(fd, 0, SEEK_END);
591 if (size <= 0)
592 {
593 BMCWEB_LOG_ERROR("Failed to get size of file, lseek() returned {}",
594 size);
595 messages::internalError(res);
596 return false;
597 }
598
599 // Arbitrary max size of 20MB to accommodate BMC dumps
600 constexpr long long int maxFileSize = 20LL * 1024LL * 1024LL;
601 if (size > maxFileSize)
602 {
603 BMCWEB_LOG_ERROR("File size {} exceeds maximum allowed size of {}",
604 size, maxFileSize);
605 messages::internalError(res);
606 return false;
607 }
608 off_t rc = lseek(fd, 0, SEEK_SET);
609 if (rc < 0)
610 {
611 BMCWEB_LOG_ERROR("Failed to reset file offset to 0");
612 messages::internalError(res);
613 return false;
614 }
615 return true;
616 }
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)617 inline void downloadEntryCallback(
618 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
619 const std::string& entryID, const std::string& downloadEntryType,
620 const boost::system::error_code& ec,
621 const sdbusplus::message::unix_fd& unixfd)
622 {
623 if (ec.value() == EBADR)
624 {
625 messages::resourceNotFound(asyncResp->res, "EntryAttachment", entryID);
626 return;
627 }
628 if (ec)
629 {
630 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
631 messages::internalError(asyncResp->res);
632 return;
633 }
634
635 // Make sure we know how to process the retrieved entry attachment
636 if ((downloadEntryType != "BMC") && (downloadEntryType != "System"))
637 {
638 BMCWEB_LOG_ERROR("downloadEntryCallback() invalid entry type: {}",
639 downloadEntryType);
640 messages::internalError(asyncResp->res);
641 }
642
643 int fd = -1;
644 fd = dup(unixfd);
645 if (fd < 0)
646 {
647 BMCWEB_LOG_ERROR("Failed to open file");
648 messages::internalError(asyncResp->res);
649 return;
650 }
651 if (!checkSizeLimit(fd, asyncResp->res))
652 {
653 close(fd);
654 return;
655 }
656 if (downloadEntryType == "System")
657 {
658 if (!asyncResp->res.openFd(fd, bmcweb::EncodingType::Base64))
659 {
660 messages::internalError(asyncResp->res);
661 close(fd);
662 return;
663 }
664 asyncResp->res.addHeader(
665 boost::beast::http::field::content_transfer_encoding, "Base64");
666 return;
667 }
668 if (!asyncResp->res.openFd(fd))
669 {
670 messages::internalError(asyncResp->res);
671 close(fd);
672 return;
673 }
674 asyncResp->res.addHeader(boost::beast::http::field::content_type,
675 "application/octet-stream");
676 }
677
678 inline void
downloadDumpEntry(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & entryID,const std::string & dumpType)679 downloadDumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
680 const std::string& entryID, const std::string& dumpType)
681 {
682 if (dumpType != "BMC")
683 {
684 BMCWEB_LOG_WARNING("Can't find Dump Entry {}", entryID);
685 messages::resourceNotFound(asyncResp->res, dumpType + " dump", entryID);
686 return;
687 }
688
689 std::string dumpEntryPath =
690 std::format("{}/entry/{}", getDumpPath(dumpType), entryID);
691
692 auto downloadDumpEntryHandler =
693 [asyncResp, entryID,
694 dumpType](const boost::system::error_code& ec,
695 const sdbusplus::message::unix_fd& unixfd) {
696 downloadEntryCallback(asyncResp, entryID, dumpType, ec, unixfd);
697 };
698
699 crow::connections::systemBus->async_method_call(
700 std::move(downloadDumpEntryHandler), "xyz.openbmc_project.Dump.Manager",
701 dumpEntryPath, "xyz.openbmc_project.Dump.Entry", "GetFileHandle");
702 }
703
downloadEventLogEntry(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName,const std::string & entryID,const std::string & dumpType)704 inline void downloadEventLogEntry(
705 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
706 const std::string& systemName, const std::string& entryID,
707 const std::string& dumpType)
708 {
709 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
710 {
711 // Option currently returns no systems. TBD
712 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
713 systemName);
714 return;
715 }
716 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
717 {
718 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
719 systemName);
720 return;
721 }
722
723 std::string entryPath =
724 sdbusplus::message::object_path("/xyz/openbmc_project/logging/entry") /
725 entryID;
726
727 auto downloadEventLogEntryHandler =
728 [asyncResp, entryID,
729 dumpType](const boost::system::error_code& ec,
730 const sdbusplus::message::unix_fd& unixfd) {
731 downloadEntryCallback(asyncResp, entryID, dumpType, ec, unixfd);
732 };
733
734 crow::connections::systemBus->async_method_call(
735 std::move(downloadEventLogEntryHandler), "xyz.openbmc_project.Logging",
736 entryPath, "xyz.openbmc_project.Logging.Entry", "GetEntry");
737 }
738
739 inline DumpCreationProgress
mapDbusStatusToDumpProgress(const std::string & status)740 mapDbusStatusToDumpProgress(const std::string& status)
741 {
742 if (status ==
743 "xyz.openbmc_project.Common.Progress.OperationStatus.Failed" ||
744 status == "xyz.openbmc_project.Common.Progress.OperationStatus.Aborted")
745 {
746 return DumpCreationProgress::DUMP_CREATE_FAILED;
747 }
748 if (status ==
749 "xyz.openbmc_project.Common.Progress.OperationStatus.Completed")
750 {
751 return DumpCreationProgress::DUMP_CREATE_SUCCESS;
752 }
753 return DumpCreationProgress::DUMP_CREATE_INPROGRESS;
754 }
755
756 inline DumpCreationProgress
getDumpCompletionStatus(const dbus::utility::DBusPropertiesMap & values)757 getDumpCompletionStatus(const dbus::utility::DBusPropertiesMap& values)
758 {
759 for (const auto& [key, val] : values)
760 {
761 if (key == "Status")
762 {
763 const std::string* value = std::get_if<std::string>(&val);
764 if (value == nullptr)
765 {
766 BMCWEB_LOG_ERROR("Status property value is null");
767 return DumpCreationProgress::DUMP_CREATE_FAILED;
768 }
769 return mapDbusStatusToDumpProgress(*value);
770 }
771 }
772 return DumpCreationProgress::DUMP_CREATE_INPROGRESS;
773 }
774
getDumpEntryPath(const std::string & dumpPath)775 inline std::string getDumpEntryPath(const std::string& dumpPath)
776 {
777 if (dumpPath == "/xyz/openbmc_project/dump/bmc/entry")
778 {
779 return std::format("/redfish/v1/Managers/{}/LogServices/Dump/Entries/",
780 BMCWEB_REDFISH_MANAGER_URI_NAME);
781 }
782 if (dumpPath == "/xyz/openbmc_project/dump/system/entry")
783 {
784 return std::format("/redfish/v1/Systems/{}/LogServices/Dump/Entries/",
785 BMCWEB_REDFISH_SYSTEM_URI_NAME);
786 }
787 return "";
788 }
789
createDumpTaskCallback(task::Payload && payload,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const sdbusplus::message::object_path & createdObjPath)790 inline void createDumpTaskCallback(
791 task::Payload&& payload,
792 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
793 const sdbusplus::message::object_path& createdObjPath)
794 {
795 const std::string dumpPath = createdObjPath.parent_path().str;
796 const std::string dumpId = createdObjPath.filename();
797
798 std::string dumpEntryPath = getDumpEntryPath(dumpPath);
799
800 if (dumpEntryPath.empty())
801 {
802 BMCWEB_LOG_ERROR("Invalid dump type received");
803 messages::internalError(asyncResp->res);
804 return;
805 }
806
807 crow::connections::systemBus->async_method_call(
808 [asyncResp, payload = std::move(payload), createdObjPath,
809 dumpEntryPath{std::move(dumpEntryPath)},
810 dumpId](const boost::system::error_code& ec,
811 const std::string& introspectXml) {
812 if (ec)
813 {
814 BMCWEB_LOG_ERROR("Introspect call failed with error: {}",
815 ec.message());
816 messages::internalError(asyncResp->res);
817 return;
818 }
819
820 // Check if the created dump object has implemented Progress
821 // interface to track dump completion. If yes, fetch the "Status"
822 // property of the interface, modify the task state accordingly.
823 // Else, return task completed.
824 tinyxml2::XMLDocument doc;
825
826 doc.Parse(introspectXml.data(), introspectXml.size());
827 tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
828 if (pRoot == nullptr)
829 {
830 BMCWEB_LOG_ERROR("XML document failed to parse");
831 messages::internalError(asyncResp->res);
832 return;
833 }
834 tinyxml2::XMLElement* interfaceNode =
835 pRoot->FirstChildElement("interface");
836
837 bool isProgressIntfPresent = false;
838 while (interfaceNode != nullptr)
839 {
840 const char* thisInterfaceName =
841 interfaceNode->Attribute("name");
842 if (thisInterfaceName != nullptr)
843 {
844 if (thisInterfaceName ==
845 std::string_view("xyz.openbmc_project.Common.Progress"))
846 {
847 interfaceNode =
848 interfaceNode->NextSiblingElement("interface");
849 continue;
850 }
851 isProgressIntfPresent = true;
852 break;
853 }
854 interfaceNode = interfaceNode->NextSiblingElement("interface");
855 }
856
857 std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
858 [createdObjPath, dumpEntryPath, dumpId, isProgressIntfPresent](
859 const boost::system::error_code& ec2,
860 sdbusplus::message_t& msg,
861 const std::shared_ptr<task::TaskData>& taskData) {
862 if (ec2)
863 {
864 BMCWEB_LOG_ERROR("{}: Error in creating dump",
865 createdObjPath.str);
866 taskData->messages.emplace_back(
867 messages::internalError());
868 taskData->state = "Cancelled";
869 return task::completed;
870 }
871
872 if (isProgressIntfPresent)
873 {
874 dbus::utility::DBusPropertiesMap values;
875 std::string prop;
876 msg.read(prop, values);
877
878 DumpCreationProgress dumpStatus =
879 getDumpCompletionStatus(values);
880 if (dumpStatus ==
881 DumpCreationProgress::DUMP_CREATE_FAILED)
882 {
883 BMCWEB_LOG_ERROR("{}: Error in creating dump",
884 createdObjPath.str);
885 taskData->state = "Cancelled";
886 return task::completed;
887 }
888
889 if (dumpStatus ==
890 DumpCreationProgress::DUMP_CREATE_INPROGRESS)
891 {
892 BMCWEB_LOG_DEBUG(
893 "{}: Dump creation task is in progress",
894 createdObjPath.str);
895 return !task::completed;
896 }
897 }
898
899 nlohmann::json retMessage = messages::success();
900 taskData->messages.emplace_back(retMessage);
901
902 boost::urls::url url = boost::urls::format(
903 "/redfish/v1/Managers/{}/LogServices/Dump/Entries/{}",
904 BMCWEB_REDFISH_MANAGER_URI_NAME, dumpId);
905
906 std::string headerLoc = "Location: ";
907 headerLoc += url.buffer();
908
909 taskData->payload->httpHeaders.emplace_back(
910 std::move(headerLoc));
911
912 BMCWEB_LOG_DEBUG("{}: Dump creation task completed",
913 createdObjPath.str);
914 taskData->state = "Completed";
915 return task::completed;
916 },
917 "type='signal',interface='org.freedesktop.DBus.Properties',"
918 "member='PropertiesChanged',path='" +
919 createdObjPath.str + "'");
920
921 // The task timer is set to max time limit within which the
922 // requested dump will be collected.
923 task->startTimer(std::chrono::minutes(6));
924 task->populateResp(asyncResp->res);
925 task->payload.emplace(payload);
926 },
927 "xyz.openbmc_project.Dump.Manager", createdObjPath,
928 "org.freedesktop.DBus.Introspectable", "Introspect");
929 }
930
createDump(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req,const std::string & dumpType)931 inline void createDump(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
932 const crow::Request& req, const std::string& dumpType)
933 {
934 std::string dumpPath = getDumpEntriesPath(dumpType);
935 if (dumpPath.empty())
936 {
937 messages::internalError(asyncResp->res);
938 return;
939 }
940
941 std::optional<std::string> diagnosticDataType;
942 std::optional<std::string> oemDiagnosticDataType;
943
944 if (!redfish::json_util::readJsonAction( //
945 req, asyncResp->res, //
946 "DiagnosticDataType", diagnosticDataType, //
947 "OEMDiagnosticDataType", oemDiagnosticDataType //
948 ))
949 {
950 return;
951 }
952
953 if (dumpType == "System")
954 {
955 if (!oemDiagnosticDataType || !diagnosticDataType)
956 {
957 BMCWEB_LOG_ERROR(
958 "CreateDump action parameter 'DiagnosticDataType'/'OEMDiagnosticDataType' value not found!");
959 messages::actionParameterMissing(
960 asyncResp->res, "CollectDiagnosticData",
961 "DiagnosticDataType & OEMDiagnosticDataType");
962 return;
963 }
964 if ((*oemDiagnosticDataType != "System") ||
965 (*diagnosticDataType != "OEM"))
966 {
967 BMCWEB_LOG_ERROR("Wrong parameter values passed");
968 messages::internalError(asyncResp->res);
969 return;
970 }
971 dumpPath = std::format("/redfish/v1/Systems/{}/LogServices/Dump/",
972 BMCWEB_REDFISH_SYSTEM_URI_NAME);
973 }
974 else if (dumpType == "BMC")
975 {
976 if (!diagnosticDataType)
977 {
978 BMCWEB_LOG_ERROR(
979 "CreateDump action parameter 'DiagnosticDataType' not found!");
980 messages::actionParameterMissing(
981 asyncResp->res, "CollectDiagnosticData", "DiagnosticDataType");
982 return;
983 }
984 if (*diagnosticDataType != "Manager")
985 {
986 BMCWEB_LOG_ERROR(
987 "Wrong parameter value passed for 'DiagnosticDataType'");
988 messages::internalError(asyncResp->res);
989 return;
990 }
991 dumpPath = std::format("/redfish/v1/Managers/{}/LogServices/Dump/",
992 BMCWEB_REDFISH_MANAGER_URI_NAME);
993 }
994 else
995 {
996 BMCWEB_LOG_ERROR("CreateDump failed. Unknown dump type");
997 messages::internalError(asyncResp->res);
998 return;
999 }
1000
1001 std::vector<std::pair<std::string, std::variant<std::string, uint64_t>>>
1002 createDumpParamVec;
1003
1004 if (req.session != nullptr)
1005 {
1006 createDumpParamVec.emplace_back(
1007 "xyz.openbmc_project.Dump.Create.CreateParameters.OriginatorId",
1008 req.session->clientIp);
1009 createDumpParamVec.emplace_back(
1010 "xyz.openbmc_project.Dump.Create.CreateParameters.OriginatorType",
1011 "xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.Client");
1012 }
1013
1014 crow::connections::systemBus->async_method_call(
1015 [asyncResp, payload(task::Payload(req)),
1016 dumpPath](const boost::system::error_code& ec,
1017 const sdbusplus::message_t& msg,
1018 const sdbusplus::message::object_path& objPath) mutable {
1019 if (ec)
1020 {
1021 BMCWEB_LOG_ERROR("CreateDump resp_handler got error {}", ec);
1022 const sd_bus_error* dbusError = msg.get_error();
1023 if (dbusError == nullptr)
1024 {
1025 messages::internalError(asyncResp->res);
1026 return;
1027 }
1028
1029 BMCWEB_LOG_ERROR("CreateDump DBus error: {} and error msg: {}",
1030 dbusError->name, dbusError->message);
1031 if (std::string_view(
1032 "xyz.openbmc_project.Common.Error.NotAllowed") ==
1033 dbusError->name)
1034 {
1035 messages::resourceInStandby(asyncResp->res);
1036 return;
1037 }
1038 if (std::string_view(
1039 "xyz.openbmc_project.Dump.Create.Error.Disabled") ==
1040 dbusError->name)
1041 {
1042 messages::serviceDisabled(asyncResp->res, dumpPath);
1043 return;
1044 }
1045 if (std::string_view(
1046 "xyz.openbmc_project.Common.Error.Unavailable") ==
1047 dbusError->name)
1048 {
1049 messages::resourceInUse(asyncResp->res);
1050 return;
1051 }
1052 // Other Dbus errors such as:
1053 // xyz.openbmc_project.Common.Error.InvalidArgument &
1054 // org.freedesktop.DBus.Error.InvalidArgs are all related to
1055 // the dbus call that is made here in the bmcweb
1056 // implementation and has nothing to do with the client's
1057 // input in the request. Hence, returning internal error
1058 // back to the client.
1059 messages::internalError(asyncResp->res);
1060 return;
1061 }
1062 BMCWEB_LOG_DEBUG("Dump Created. Path: {}", objPath.str);
1063 createDumpTaskCallback(std::move(payload), asyncResp, objPath);
1064 },
1065 "xyz.openbmc_project.Dump.Manager", getDumpPath(dumpType),
1066 "xyz.openbmc_project.Dump.Create", "CreateDump", createDumpParamVec);
1067 }
1068
clearDump(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & dumpType)1069 inline void clearDump(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1070 const std::string& dumpType)
1071 {
1072 crow::connections::systemBus->async_method_call(
1073 [asyncResp](const boost::system::error_code& ec) {
1074 if (ec)
1075 {
1076 BMCWEB_LOG_ERROR("clearDump resp_handler got error {}", ec);
1077 messages::internalError(asyncResp->res);
1078 return;
1079 }
1080 },
1081 "xyz.openbmc_project.Dump.Manager", getDumpPath(dumpType),
1082 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
1083 }
1084
parseCrashdumpParameters(const dbus::utility::DBusPropertiesMap & params,std::string & filename,std::string & timestamp,std::string & logfile)1085 inline void parseCrashdumpParameters(
1086 const dbus::utility::DBusPropertiesMap& params, std::string& filename,
1087 std::string& timestamp, std::string& logfile)
1088 {
1089 const std::string* filenamePtr = nullptr;
1090 const std::string* timestampPtr = nullptr;
1091 const std::string* logfilePtr = nullptr;
1092
1093 const bool success = sdbusplus::unpackPropertiesNoThrow(
1094 dbus_utils::UnpackErrorPrinter(), params, "Timestamp", timestampPtr,
1095 "Filename", filenamePtr, "Log", logfilePtr);
1096
1097 if (!success)
1098 {
1099 return;
1100 }
1101
1102 if (filenamePtr != nullptr)
1103 {
1104 filename = *filenamePtr;
1105 }
1106
1107 if (timestampPtr != nullptr)
1108 {
1109 timestamp = *timestampPtr;
1110 }
1111
1112 if (logfilePtr != nullptr)
1113 {
1114 logfile = *logfilePtr;
1115 }
1116 }
1117
requestRoutesSystemLogServiceCollection(App & app)1118 inline void requestRoutesSystemLogServiceCollection(App& app)
1119 {
1120 /**
1121 * Functions triggers appropriate requests on DBus
1122 */
1123 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/")
1124 .privileges(redfish::privileges::getLogServiceCollection)
1125 .methods(
1126 boost::beast::http::verb::
1127 get)([&app](const crow::Request& req,
1128 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1129 const std::string& systemName) {
1130 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1131 {
1132 return;
1133 }
1134 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1135 {
1136 // Option currently returns no systems. TBD
1137 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1138 systemName);
1139 return;
1140 }
1141 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1142 {
1143 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1144 systemName);
1145 return;
1146 }
1147
1148 // Collections don't include the static data added by SubRoute
1149 // because it has a duplicate entry for members
1150 asyncResp->res.jsonValue["@odata.type"] =
1151 "#LogServiceCollection.LogServiceCollection";
1152 asyncResp->res.jsonValue["@odata.id"] =
1153 std::format("/redfish/v1/Systems/{}/LogServices",
1154 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1155 asyncResp->res.jsonValue["Name"] = "System Log Services Collection";
1156 asyncResp->res.jsonValue["Description"] =
1157 "Collection of LogServices for this Computer System";
1158 nlohmann::json& logServiceArray =
1159 asyncResp->res.jsonValue["Members"];
1160 logServiceArray = nlohmann::json::array();
1161 nlohmann::json::object_t eventLog;
1162 eventLog["@odata.id"] =
1163 std::format("/redfish/v1/Systems/{}/LogServices/EventLog",
1164 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1165 logServiceArray.emplace_back(std::move(eventLog));
1166 if constexpr (BMCWEB_REDFISH_DUMP_LOG)
1167 {
1168 nlohmann::json::object_t dumpLog;
1169 dumpLog["@odata.id"] =
1170 std::format("/redfish/v1/Systems/{}/LogServices/Dump",
1171 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1172 logServiceArray.emplace_back(std::move(dumpLog));
1173 }
1174
1175 if constexpr (BMCWEB_REDFISH_CPU_LOG)
1176 {
1177 nlohmann::json::object_t crashdump;
1178 crashdump["@odata.id"] =
1179 std::format("/redfish/v1/Systems/{}/LogServices/Crashdump",
1180 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1181 logServiceArray.emplace_back(std::move(crashdump));
1182 }
1183
1184 if constexpr (BMCWEB_REDFISH_HOST_LOGGER)
1185 {
1186 nlohmann::json::object_t hostlogger;
1187 hostlogger["@odata.id"] =
1188 std::format("/redfish/v1/Systems/{}/LogServices/HostLogger",
1189 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1190 logServiceArray.emplace_back(std::move(hostlogger));
1191 }
1192 asyncResp->res.jsonValue["Members@odata.count"] =
1193 logServiceArray.size();
1194
1195 constexpr std::array<std::string_view, 1> interfaces = {
1196 "xyz.openbmc_project.State.Boot.PostCode"};
1197 dbus::utility::getSubTreePaths(
1198 "/", 0, interfaces,
1199 [asyncResp](const boost::system::error_code& ec,
1200 const dbus::utility::MapperGetSubTreePathsResponse&
1201 subtreePath) {
1202 if (ec)
1203 {
1204 BMCWEB_LOG_ERROR("{}", ec);
1205 return;
1206 }
1207
1208 for (const auto& pathStr : subtreePath)
1209 {
1210 if (pathStr.find("PostCode") != std::string::npos)
1211 {
1212 nlohmann::json& logServiceArrayLocal =
1213 asyncResp->res.jsonValue["Members"];
1214 nlohmann::json::object_t member;
1215 member["@odata.id"] = std::format(
1216 "/redfish/v1/Systems/{}/LogServices/PostCodes",
1217 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1218
1219 logServiceArrayLocal.emplace_back(
1220 std::move(member));
1221
1222 asyncResp->res.jsonValue["Members@odata.count"] =
1223 logServiceArrayLocal.size();
1224 return;
1225 }
1226 }
1227 });
1228 });
1229 }
1230
requestRoutesEventLogService(App & app)1231 inline void requestRoutesEventLogService(App& app)
1232 {
1233 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/")
1234 .privileges(redfish::privileges::getLogService)
1235 .methods(
1236 boost::beast::http::verb::
1237 get)([&app](const crow::Request& req,
1238 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1239 const std::string& systemName) {
1240 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1241 {
1242 return;
1243 }
1244 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1245 {
1246 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1247 systemName);
1248 return;
1249 }
1250 asyncResp->res.jsonValue["@odata.id"] =
1251 std::format("/redfish/v1/Systems/{}/LogServices/EventLog",
1252 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1253 asyncResp->res.jsonValue["@odata.type"] =
1254 "#LogService.v1_2_0.LogService";
1255 asyncResp->res.jsonValue["Name"] = "Event Log Service";
1256 asyncResp->res.jsonValue["Description"] =
1257 "System Event Log Service";
1258 asyncResp->res.jsonValue["Id"] = "EventLog";
1259 asyncResp->res.jsonValue["OverWritePolicy"] =
1260 log_service::OverWritePolicy::WrapsWhenFull;
1261
1262 std::pair<std::string, std::string> redfishDateTimeOffset =
1263 redfish::time_utils::getDateTimeOffsetNow();
1264
1265 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
1266 asyncResp->res.jsonValue["DateTimeLocalOffset"] =
1267 redfishDateTimeOffset.second;
1268
1269 asyncResp->res.jsonValue["Entries"]["@odata.id"] = std::format(
1270 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries",
1271 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1272 asyncResp->res
1273 .jsonValue["Actions"]["#LogService.ClearLog"]["target"]
1274
1275 = std::format(
1276 "/redfish/v1/Systems/{}/LogServices/EventLog/Actions/LogService.ClearLog",
1277 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1278 });
1279 }
1280
handleSystemsLogServicesEventLogActionsClearPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName)1281 inline void handleSystemsLogServicesEventLogActionsClearPost(
1282 App& app, const crow::Request& req,
1283 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1284 const std::string& systemName)
1285 {
1286 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1287 {
1288 return;
1289 }
1290 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1291 {
1292 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1293 systemName);
1294 return;
1295 }
1296
1297 // Clear the EventLog by deleting the log files
1298 std::vector<std::filesystem::path> redfishLogFiles;
1299 if (getRedfishLogFiles(redfishLogFiles))
1300 {
1301 for (const std::filesystem::path& file : redfishLogFiles)
1302 {
1303 std::error_code ec;
1304 std::filesystem::remove(file, ec);
1305 }
1306 }
1307
1308 // Reload rsyslog so it knows to start new log files
1309 crow::connections::systemBus->async_method_call(
1310 [asyncResp](const boost::system::error_code& ec) {
1311 if (ec)
1312 {
1313 BMCWEB_LOG_ERROR("Failed to reload rsyslog: {}", ec);
1314 messages::internalError(asyncResp->res);
1315 return;
1316 }
1317
1318 messages::success(asyncResp->res);
1319 },
1320 "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
1321 "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service",
1322 "replace");
1323 }
1324
requestRoutesJournalEventLogClear(App & app)1325 inline void requestRoutesJournalEventLogClear(App& app)
1326 {
1327 BMCWEB_ROUTE(
1328 app,
1329 "/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/")
1330 .privileges({{"ConfigureComponents"}})
1331 .methods(boost::beast::http::verb::post)(std::bind_front(
1332 handleSystemsLogServicesEventLogActionsClearPost, std::ref(app)));
1333 }
1334
1335 enum class LogParseError
1336 {
1337 success,
1338 parseFailed,
1339 messageIdNotInRegistry,
1340 };
1341
fillEventLogEntryJson(const std::string & logEntryID,const std::string & logEntry,nlohmann::json::object_t & logEntryJson)1342 static LogParseError fillEventLogEntryJson(
1343 const std::string& logEntryID, const std::string& logEntry,
1344 nlohmann::json::object_t& logEntryJson)
1345 {
1346 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
1347 // First get the Timestamp
1348 size_t space = logEntry.find_first_of(' ');
1349 if (space == std::string::npos)
1350 {
1351 return LogParseError::parseFailed;
1352 }
1353 std::string timestamp = logEntry.substr(0, space);
1354 // Then get the log contents
1355 size_t entryStart = logEntry.find_first_not_of(' ', space);
1356 if (entryStart == std::string::npos)
1357 {
1358 return LogParseError::parseFailed;
1359 }
1360 std::string_view entry(logEntry);
1361 entry.remove_prefix(entryStart);
1362 // Use split to separate the entry into its fields
1363 std::vector<std::string> logEntryFields;
1364 bmcweb::split(logEntryFields, entry, ',');
1365 // We need at least a MessageId to be valid
1366 auto logEntryIter = logEntryFields.begin();
1367 if (logEntryIter == logEntryFields.end())
1368 {
1369 return LogParseError::parseFailed;
1370 }
1371 std::string& messageID = *logEntryIter;
1372 // Get the Message from the MessageRegistry
1373 const registries::Message* message = registries::getMessage(messageID);
1374
1375 logEntryIter++;
1376 if (message == nullptr)
1377 {
1378 BMCWEB_LOG_WARNING("Log entry not found in registry: {}", logEntry);
1379 return LogParseError::messageIdNotInRegistry;
1380 }
1381
1382 std::vector<std::string_view> messageArgs(logEntryIter,
1383 logEntryFields.end());
1384 messageArgs.resize(message->numberOfArgs);
1385
1386 std::string msg =
1387 redfish::registries::fillMessageArgs(messageArgs, message->message);
1388 if (msg.empty())
1389 {
1390 return LogParseError::parseFailed;
1391 }
1392
1393 // Get the Created time from the timestamp. The log timestamp is in RFC3339
1394 // format which matches the Redfish format except for the fractional seconds
1395 // between the '.' and the '+', so just remove them.
1396 std::size_t dot = timestamp.find_first_of('.');
1397 std::size_t plus = timestamp.find_first_of('+');
1398 if (dot != std::string::npos && plus != std::string::npos)
1399 {
1400 timestamp.erase(dot, plus - dot);
1401 }
1402
1403 // Fill in the log entry with the gathered data
1404 logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
1405 logEntryJson["@odata.id"] = boost::urls::format(
1406 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}",
1407 BMCWEB_REDFISH_SYSTEM_URI_NAME, logEntryID);
1408 logEntryJson["Name"] = "System Event Log Entry";
1409 logEntryJson["Id"] = logEntryID;
1410 logEntryJson["Message"] = std::move(msg);
1411 logEntryJson["MessageId"] = std::move(messageID);
1412 logEntryJson["MessageArgs"] = messageArgs;
1413 logEntryJson["EntryType"] = "Event";
1414 logEntryJson["Severity"] = message->messageSeverity;
1415 logEntryJson["Created"] = std::move(timestamp);
1416 return LogParseError::success;
1417 }
1418
fillEventLogLogEntryFromPropertyMap(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const dbus::utility::DBusPropertiesMap & resp,nlohmann::json & objectToFillOut)1419 inline void fillEventLogLogEntryFromPropertyMap(
1420 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1421 const dbus::utility::DBusPropertiesMap& resp,
1422 nlohmann::json& objectToFillOut)
1423 {
1424 std::optional<DbusEventLogEntry> optEntry =
1425 fillDbusEventLogEntryFromPropertyMap(resp);
1426
1427 if (!optEntry.has_value())
1428 {
1429 messages::internalError(asyncResp->res);
1430 return;
1431 }
1432 DbusEventLogEntry entry = optEntry.value();
1433
1434 objectToFillOut["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
1435 objectToFillOut["@odata.id"] = boost::urls::format(
1436 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}",
1437 BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(entry.Id));
1438 objectToFillOut["Name"] = "System Event Log Entry";
1439 objectToFillOut["Id"] = std::to_string(entry.Id);
1440 objectToFillOut["Message"] = entry.Message;
1441 objectToFillOut["Resolved"] = entry.Resolved;
1442 std::optional<bool> notifyAction =
1443 getProviderNotifyAction(entry.ServiceProviderNotify);
1444 if (notifyAction)
1445 {
1446 objectToFillOut["ServiceProviderNotified"] = *notifyAction;
1447 }
1448 if ((entry.Resolution != nullptr) && !entry.Resolution->empty())
1449 {
1450 objectToFillOut["Resolution"] = *entry.Resolution;
1451 }
1452 objectToFillOut["EntryType"] = "Event";
1453 objectToFillOut["Severity"] =
1454 translateSeverityDbusToRedfish(entry.Severity);
1455 objectToFillOut["Created"] =
1456 redfish::time_utils::getDateTimeUintMs(entry.Timestamp);
1457 objectToFillOut["Modified"] =
1458 redfish::time_utils::getDateTimeUintMs(entry.UpdateTimestamp);
1459 if (entry.Path != nullptr)
1460 {
1461 objectToFillOut["AdditionalDataURI"] = boost::urls::format(
1462 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}/attachment",
1463 BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(entry.Id));
1464 }
1465 }
1466
afterLogEntriesGetManagedObjects(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const dbus::utility::ManagedObjectType & resp)1467 inline void afterLogEntriesGetManagedObjects(
1468 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1469 const boost::system::error_code& ec,
1470 const dbus::utility::ManagedObjectType& resp)
1471 {
1472 if (ec)
1473 {
1474 // TODO Handle for specific error code
1475 BMCWEB_LOG_ERROR("getLogEntriesIfaceData resp_handler got error {}",
1476 ec);
1477 messages::internalError(asyncResp->res);
1478 return;
1479 }
1480 nlohmann::json::array_t entriesArray;
1481 for (const auto& objectPath : resp)
1482 {
1483 dbus::utility::DBusPropertiesMap propsFlattened;
1484 auto isEntry =
1485 std::ranges::find_if(objectPath.second, [](const auto& object) {
1486 return object.first == "xyz.openbmc_project.Logging.Entry";
1487 });
1488 if (isEntry == objectPath.second.end())
1489 {
1490 continue;
1491 }
1492 for (const auto& interfaceMap : objectPath.second)
1493 {
1494 for (const auto& propertyMap : interfaceMap.second)
1495 {
1496 propsFlattened.emplace_back(propertyMap.first,
1497 propertyMap.second);
1498 }
1499 }
1500 fillEventLogLogEntryFromPropertyMap(asyncResp, propsFlattened,
1501 entriesArray.emplace_back());
1502 }
1503
1504 std::ranges::sort(entriesArray, [](const nlohmann::json& left,
1505 const nlohmann::json& right) {
1506 return (left["Id"] <= right["Id"]);
1507 });
1508 asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size();
1509 asyncResp->res.jsonValue["Members"] = std::move(entriesArray);
1510 }
1511
handleSystemsLogServiceEventLogLogEntryCollection(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName)1512 inline void handleSystemsLogServiceEventLogLogEntryCollection(
1513 App& app, const crow::Request& req,
1514 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1515 const std::string& systemName)
1516 {
1517 query_param::QueryCapabilities capabilities = {
1518 .canDelegateTop = true,
1519 .canDelegateSkip = true,
1520 };
1521 query_param::Query delegatedQuery;
1522 if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
1523 delegatedQuery, capabilities))
1524 {
1525 return;
1526 }
1527 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1528 {
1529 // Option currently returns no systems. TBD
1530 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1531 systemName);
1532 return;
1533 }
1534 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1535 {
1536 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1537 systemName);
1538 return;
1539 }
1540
1541 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
1542 size_t skip = delegatedQuery.skip.value_or(0);
1543
1544 // Collections don't include the static data added by SubRoute
1545 // because it has a duplicate entry for members
1546 asyncResp->res.jsonValue["@odata.type"] =
1547 "#LogEntryCollection.LogEntryCollection";
1548 asyncResp->res.jsonValue["@odata.id"] =
1549 std::format("/redfish/v1/Systems/{}/LogServices/EventLog/Entries",
1550 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1551 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
1552 asyncResp->res.jsonValue["Description"] =
1553 "Collection of System Event Log Entries";
1554
1555 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
1556 logEntryArray = nlohmann::json::array();
1557 // Go through the log files and create a unique ID for each
1558 // entry
1559 std::vector<std::filesystem::path> redfishLogFiles;
1560 getRedfishLogFiles(redfishLogFiles);
1561 uint64_t entryCount = 0;
1562 std::string logEntry;
1563
1564 // Oldest logs are in the last file, so start there and loop
1565 // backwards
1566 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); it++)
1567 {
1568 std::ifstream logStream(*it);
1569 if (!logStream.is_open())
1570 {
1571 continue;
1572 }
1573
1574 // Reset the unique ID on the first entry
1575 bool firstEntry = true;
1576 while (std::getline(logStream, logEntry))
1577 {
1578 std::string idStr;
1579 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
1580 {
1581 continue;
1582 }
1583 firstEntry = false;
1584
1585 nlohmann::json::object_t bmcLogEntry;
1586 LogParseError status =
1587 fillEventLogEntryJson(idStr, logEntry, bmcLogEntry);
1588 if (status == LogParseError::messageIdNotInRegistry)
1589 {
1590 continue;
1591 }
1592 if (status != LogParseError::success)
1593 {
1594 messages::internalError(asyncResp->res);
1595 return;
1596 }
1597
1598 entryCount++;
1599 // Handle paging using skip (number of entries to skip from the
1600 // start) and top (number of entries to display)
1601 if (entryCount <= skip || entryCount > skip + top)
1602 {
1603 continue;
1604 }
1605
1606 logEntryArray.emplace_back(std::move(bmcLogEntry));
1607 }
1608 }
1609 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
1610 if (skip + top < entryCount)
1611 {
1612 asyncResp->res.jsonValue["Members@odata.nextLink"] =
1613 boost::urls::format(
1614 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries?$skip={}",
1615 BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(skip + top));
1616 }
1617 }
1618
requestRoutesJournalEventLogEntryCollection(App & app)1619 inline void requestRoutesJournalEventLogEntryCollection(App& app)
1620 {
1621 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/")
1622 .privileges(redfish::privileges::getLogEntryCollection)
1623 .methods(boost::beast::http::verb::get)(std::bind_front(
1624 handleSystemsLogServiceEventLogLogEntryCollection, std::ref(app)));
1625 }
1626
handleSystemsLogServiceEventLogEntriesGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName,const std::string & param)1627 inline void handleSystemsLogServiceEventLogEntriesGet(
1628 App& app, const crow::Request& req,
1629 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1630 const std::string& systemName, const std::string& param)
1631 {
1632 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1633 {
1634 return;
1635 }
1636 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1637 {
1638 // Option currently returns no systems. TBD
1639 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1640 systemName);
1641 return;
1642 }
1643
1644 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1645 {
1646 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1647 systemName);
1648 return;
1649 }
1650
1651 const std::string& targetID = param;
1652
1653 // Go through the log files and check the unique ID for each
1654 // entry to find the target entry
1655 std::vector<std::filesystem::path> redfishLogFiles;
1656 getRedfishLogFiles(redfishLogFiles);
1657 std::string logEntry;
1658
1659 // Oldest logs are in the last file, so start there and loop
1660 // backwards
1661 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); it++)
1662 {
1663 std::ifstream logStream(*it);
1664 if (!logStream.is_open())
1665 {
1666 continue;
1667 }
1668
1669 // Reset the unique ID on the first entry
1670 bool firstEntry = true;
1671 while (std::getline(logStream, logEntry))
1672 {
1673 std::string idStr;
1674 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
1675 {
1676 continue;
1677 }
1678 firstEntry = false;
1679
1680 if (idStr == targetID)
1681 {
1682 nlohmann::json::object_t bmcLogEntry;
1683 LogParseError status =
1684 fillEventLogEntryJson(idStr, logEntry, bmcLogEntry);
1685 if (status != LogParseError::success)
1686 {
1687 messages::internalError(asyncResp->res);
1688 return;
1689 }
1690 asyncResp->res.jsonValue.update(bmcLogEntry);
1691 return;
1692 }
1693 }
1694 }
1695 // Requested ID was not found
1696 messages::resourceNotFound(asyncResp->res, "LogEntry", targetID);
1697 }
1698
requestRoutesJournalEventLogEntry(App & app)1699 inline void requestRoutesJournalEventLogEntry(App& app)
1700 {
1701 BMCWEB_ROUTE(
1702 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
1703 .privileges(redfish::privileges::getLogEntry)
1704 .methods(boost::beast::http::verb::get)(std::bind_front(
1705 handleSystemsLogServiceEventLogEntriesGet, std::ref(app)));
1706 }
1707
dBusEventLogEntryCollection(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1708 inline void dBusEventLogEntryCollection(
1709 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1710 {
1711 // Collections don't include the static data added by SubRoute
1712 // because it has a duplicate entry for members
1713 asyncResp->res.jsonValue["@odata.type"] =
1714 "#LogEntryCollection.LogEntryCollection";
1715 asyncResp->res.jsonValue["@odata.id"] =
1716 std::format("/redfish/v1/Systems/{}/LogServices/EventLog/Entries",
1717 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1718 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
1719 asyncResp->res.jsonValue["Description"] =
1720 "Collection of System Event Log Entries";
1721
1722 // DBus implementation of EventLog/Entries
1723 // Make call to Logging Service to find all log entry objects
1724 sdbusplus::message::object_path path("/xyz/openbmc_project/logging");
1725 dbus::utility::getManagedObjects(
1726 "xyz.openbmc_project.Logging", path,
1727 [asyncResp](const boost::system::error_code& ec,
1728 const dbus::utility::ManagedObjectType& resp) {
1729 afterLogEntriesGetManagedObjects(asyncResp, ec, resp);
1730 });
1731 }
1732
requestRoutesDBusEventLogEntryCollection(App & app)1733 inline void requestRoutesDBusEventLogEntryCollection(App& app)
1734 {
1735 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/")
1736 .privileges(redfish::privileges::getLogEntryCollection)
1737 .methods(boost::beast::http::verb::get)(
1738 [&app](const crow::Request& req,
1739 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1740 const std::string& systemName) {
1741 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1742 {
1743 return;
1744 }
1745 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1746 {
1747 // Option currently returns no systems. TBD
1748 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1749 systemName);
1750 return;
1751 }
1752 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1753 {
1754 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1755 systemName);
1756 return;
1757 }
1758 dBusEventLogEntryCollection(asyncResp);
1759 });
1760 }
1761
dBusEventLogEntryGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string entryID)1762 inline void dBusEventLogEntryGet(
1763 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string entryID)
1764 {
1765 dbus::utility::escapePathForDbus(entryID);
1766
1767 // DBus implementation of EventLog/Entries
1768 // Make call to Logging Service to find all log entry objects
1769 sdbusplus::asio::getAllProperties(
1770 *crow::connections::systemBus, "xyz.openbmc_project.Logging",
1771 "/xyz/openbmc_project/logging/entry/" + entryID, "",
1772 [asyncResp, entryID](const boost::system::error_code& ec,
1773 const dbus::utility::DBusPropertiesMap& resp) {
1774 if (ec.value() == EBADR)
1775 {
1776 messages::resourceNotFound(asyncResp->res, "EventLogEntry",
1777 entryID);
1778 return;
1779 }
1780 if (ec)
1781 {
1782 BMCWEB_LOG_ERROR(
1783 "EventLogEntry (DBus) resp_handler got error {}", ec);
1784 messages::internalError(asyncResp->res);
1785 return;
1786 }
1787
1788 fillEventLogLogEntryFromPropertyMap(asyncResp, resp,
1789 asyncResp->res.jsonValue);
1790 });
1791 }
1792
1793 inline void
dBusEventLogEntryPatch(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & entryId)1794 dBusEventLogEntryPatch(const crow::Request& req,
1795 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1796 const std::string& entryId)
1797 {
1798 std::optional<bool> resolved;
1799
1800 if (!json_util::readJsonPatch(req, asyncResp->res, "Resolved", resolved))
1801 {
1802 return;
1803 }
1804 BMCWEB_LOG_DEBUG("Set Resolved");
1805
1806 setDbusProperty(asyncResp, "Resolved", "xyz.openbmc_project.Logging",
1807 "/xyz/openbmc_project/logging/entry/" + entryId,
1808 "xyz.openbmc_project.Logging.Entry", "Resolved",
1809 resolved.value_or(false));
1810 }
1811
dBusEventLogEntryDelete(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string entryID)1812 inline void dBusEventLogEntryDelete(
1813 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string entryID)
1814 {
1815 BMCWEB_LOG_DEBUG("Do delete single event entries.");
1816
1817 dbus::utility::escapePathForDbus(entryID);
1818
1819 // Process response from Logging service.
1820 auto respHandler = [asyncResp,
1821 entryID](const boost::system::error_code& ec) {
1822 BMCWEB_LOG_DEBUG("EventLogEntry (DBus) doDelete callback: Done");
1823 if (ec)
1824 {
1825 if (ec.value() == EBADR)
1826 {
1827 messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
1828 return;
1829 }
1830 // TODO Handle for specific error code
1831 BMCWEB_LOG_ERROR(
1832 "EventLogEntry (DBus) doDelete respHandler got error {}", ec);
1833 asyncResp->res.result(
1834 boost::beast::http::status::internal_server_error);
1835 return;
1836 }
1837
1838 asyncResp->res.result(boost::beast::http::status::ok);
1839 };
1840
1841 // Make call to Logging service to request Delete Log
1842 crow::connections::systemBus->async_method_call(
1843 respHandler, "xyz.openbmc_project.Logging",
1844 "/xyz/openbmc_project/logging/entry/" + entryID,
1845 "xyz.openbmc_project.Object.Delete", "Delete");
1846 }
1847
requestRoutesDBusEventLogEntry(App & app)1848 inline void requestRoutesDBusEventLogEntry(App& app)
1849 {
1850 BMCWEB_ROUTE(
1851 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
1852 .privileges(redfish::privileges::getLogEntry)
1853 .methods(boost::beast::http::verb::get)(
1854 [&app](const crow::Request& req,
1855 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1856 const std::string& systemName, const std::string& entryId) {
1857 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1858 {
1859 return;
1860 }
1861 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1862 {
1863 // Option currently returns no systems. TBD
1864 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1865 systemName);
1866 return;
1867 }
1868 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1869 {
1870 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1871 systemName);
1872 return;
1873 }
1874
1875 dBusEventLogEntryGet(asyncResp, entryId);
1876 });
1877
1878 BMCWEB_ROUTE(
1879 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
1880 .privileges(redfish::privileges::patchLogEntry)
1881 .methods(boost::beast::http::verb::patch)(
1882 [&app](const crow::Request& req,
1883 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1884 const std::string& systemName, const std::string& entryId) {
1885 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1886 {
1887 return;
1888 }
1889 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1890 {
1891 // Option currently returns no systems. TBD
1892 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1893 systemName);
1894 return;
1895 }
1896 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1897 {
1898 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1899 systemName);
1900 return;
1901 }
1902
1903 dBusEventLogEntryPatch(req, asyncResp, entryId);
1904 });
1905
1906 BMCWEB_ROUTE(
1907 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
1908 .privileges(redfish::privileges::deleteLogEntry)
1909
1910 .methods(boost::beast::http::verb::delete_)(
1911 [&app](const crow::Request& req,
1912 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1913 const std::string& systemName, const std::string& param) {
1914 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1915 {
1916 return;
1917 }
1918 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1919 {
1920 // Option currently returns no systems. TBD
1921 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1922 systemName);
1923 return;
1924 }
1925 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1926 {
1927 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1928 systemName);
1929 return;
1930 }
1931 dBusEventLogEntryDelete(asyncResp, param);
1932 });
1933 }
1934
1935 constexpr const char* hostLoggerFolderPath = "/var/log/console";
1936
1937 inline bool
getHostLoggerFiles(const std::string & hostLoggerFilePath,std::vector<std::filesystem::path> & hostLoggerFiles)1938 getHostLoggerFiles(const std::string& hostLoggerFilePath,
1939 std::vector<std::filesystem::path>& hostLoggerFiles)
1940 {
1941 std::error_code ec;
1942 std::filesystem::directory_iterator logPath(hostLoggerFilePath, ec);
1943 if (ec)
1944 {
1945 BMCWEB_LOG_WARNING("{}", ec.message());
1946 return false;
1947 }
1948 for (const std::filesystem::directory_entry& it : logPath)
1949 {
1950 std::string filename = it.path().filename();
1951 // Prefix of each log files is "log". Find the file and save the
1952 // path
1953 if (filename.starts_with("log"))
1954 {
1955 hostLoggerFiles.emplace_back(it.path());
1956 }
1957 }
1958 // As the log files rotate, they are appended with a ".#" that is higher for
1959 // the older logs. Since we start from oldest logs, sort the name in
1960 // descending order.
1961 std::sort(hostLoggerFiles.rbegin(), hostLoggerFiles.rend(),
1962 AlphanumLess<std::string>());
1963
1964 return true;
1965 }
1966
getHostLoggerEntries(const std::vector<std::filesystem::path> & hostLoggerFiles,uint64_t skip,uint64_t top,std::vector<std::string> & logEntries,size_t & logCount)1967 inline bool getHostLoggerEntries(
1968 const std::vector<std::filesystem::path>& hostLoggerFiles, uint64_t skip,
1969 uint64_t top, std::vector<std::string>& logEntries, size_t& logCount)
1970 {
1971 GzFileReader logFile;
1972
1973 // Go though all log files and expose host logs.
1974 for (const std::filesystem::path& it : hostLoggerFiles)
1975 {
1976 if (!logFile.gzGetLines(it.string(), skip, top, logEntries, logCount))
1977 {
1978 BMCWEB_LOG_ERROR("fail to expose host logs");
1979 return false;
1980 }
1981 }
1982 // Get lastMessage from constructor by getter
1983 std::string lastMessage = logFile.getLastMessage();
1984 if (!lastMessage.empty())
1985 {
1986 logCount++;
1987 if (logCount > skip && logCount <= (skip + top))
1988 {
1989 logEntries.push_back(lastMessage);
1990 }
1991 }
1992 return true;
1993 }
1994
handleBMCLogServicesCollectionGet(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)1995 inline void handleBMCLogServicesCollectionGet(
1996 crow::App& app, const crow::Request& req,
1997 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1998 const std::string& managerId)
1999 {
2000 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2001 {
2002 return;
2003 }
2004
2005 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2006 {
2007 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2008 return;
2009 }
2010
2011 // Collections don't include the static data added by SubRoute
2012 // because it has a duplicate entry for members
2013 asyncResp->res.jsonValue["@odata.type"] =
2014 "#LogServiceCollection.LogServiceCollection";
2015 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
2016 "/redfish/v1/Managers/{}/LogServices", BMCWEB_REDFISH_MANAGER_URI_NAME);
2017 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
2018 asyncResp->res.jsonValue["Description"] =
2019 "Collection of LogServices for this Manager";
2020 nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"];
2021 logServiceArray = nlohmann::json::array();
2022
2023 if constexpr (BMCWEB_REDFISH_BMC_JOURNAL)
2024 {
2025 nlohmann::json::object_t journal;
2026 journal["@odata.id"] =
2027 boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal",
2028 BMCWEB_REDFISH_MANAGER_URI_NAME);
2029 logServiceArray.emplace_back(std::move(journal));
2030 }
2031
2032 asyncResp->res.jsonValue["Members@odata.count"] = logServiceArray.size();
2033
2034 if constexpr (BMCWEB_REDFISH_DUMP_LOG)
2035 {
2036 constexpr std::array<std::string_view, 1> interfaces = {
2037 "xyz.openbmc_project.Collection.DeleteAll"};
2038 dbus::utility::getSubTreePaths(
2039 "/xyz/openbmc_project/dump", 0, interfaces,
2040 [asyncResp](const boost::system::error_code& ec,
2041 const dbus::utility::MapperGetSubTreePathsResponse&
2042 subTreePaths) {
2043 if (ec)
2044 {
2045 BMCWEB_LOG_ERROR(
2046 "handleBMCLogServicesCollectionGet respHandler got error {}",
2047 ec);
2048 // Assume that getting an error simply means there are no
2049 // dump LogServices. Return without adding any error
2050 // response.
2051 return;
2052 }
2053
2054 nlohmann::json& logServiceArrayLocal =
2055 asyncResp->res.jsonValue["Members"];
2056
2057 for (const std::string& path : subTreePaths)
2058 {
2059 if (path == "/xyz/openbmc_project/dump/bmc")
2060 {
2061 nlohmann::json::object_t member;
2062 member["@odata.id"] = boost::urls::format(
2063 "/redfish/v1/Managers/{}/LogServices/Dump",
2064 BMCWEB_REDFISH_MANAGER_URI_NAME);
2065 logServiceArrayLocal.emplace_back(std::move(member));
2066 }
2067 else if (path == "/xyz/openbmc_project/dump/faultlog")
2068 {
2069 nlohmann::json::object_t member;
2070 member["@odata.id"] = boost::urls::format(
2071 "/redfish/v1/Managers/{}/LogServices/FaultLog",
2072 BMCWEB_REDFISH_MANAGER_URI_NAME);
2073 logServiceArrayLocal.emplace_back(std::move(member));
2074 }
2075 }
2076
2077 asyncResp->res.jsonValue["Members@odata.count"] =
2078 logServiceArrayLocal.size();
2079 });
2080 }
2081 }
2082
requestRoutesBMCLogServiceCollection(App & app)2083 inline void requestRoutesBMCLogServiceCollection(App& app)
2084 {
2085 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/")
2086 .privileges(redfish::privileges::getLogServiceCollection)
2087 .methods(boost::beast::http::verb::get)(
2088 std::bind_front(handleBMCLogServicesCollectionGet, std::ref(app)));
2089 }
2090
2091 inline void
getDumpServiceInfo(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & dumpType)2092 getDumpServiceInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2093 const std::string& dumpType)
2094 {
2095 std::string dumpPath;
2096 log_service::OverWritePolicy overWritePolicy =
2097 log_service::OverWritePolicy::Invalid;
2098 bool collectDiagnosticDataSupported = false;
2099
2100 if (dumpType == "BMC")
2101 {
2102 dumpPath = std::format("/redfish/v1/Managers/{}/LogServices/Dump",
2103 BMCWEB_REDFISH_MANAGER_URI_NAME);
2104 overWritePolicy = log_service::OverWritePolicy::WrapsWhenFull;
2105 collectDiagnosticDataSupported = true;
2106 }
2107 else if (dumpType == "FaultLog")
2108 {
2109 dumpPath = std::format("/redfish/v1/Managers/{}/LogServices/FaultLog",
2110 BMCWEB_REDFISH_MANAGER_URI_NAME);
2111 overWritePolicy = log_service::OverWritePolicy::Unknown;
2112 collectDiagnosticDataSupported = false;
2113 }
2114 else if (dumpType == "System")
2115 {
2116 dumpPath = std::format("/redfish/v1/Systems/{}/LogServices/Dump",
2117 BMCWEB_REDFISH_SYSTEM_URI_NAME);
2118 overWritePolicy = log_service::OverWritePolicy::WrapsWhenFull;
2119 collectDiagnosticDataSupported = true;
2120 }
2121 else
2122 {
2123 BMCWEB_LOG_ERROR("getDumpServiceInfo() invalid dump type: {}",
2124 dumpType);
2125 messages::internalError(asyncResp->res);
2126 return;
2127 }
2128
2129 asyncResp->res.jsonValue["@odata.id"] = dumpPath;
2130 asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
2131 asyncResp->res.jsonValue["Name"] = "Dump LogService";
2132 asyncResp->res.jsonValue["Description"] = dumpType + " Dump LogService";
2133 asyncResp->res.jsonValue["Id"] = std::filesystem::path(dumpPath).filename();
2134 asyncResp->res.jsonValue["OverWritePolicy"] = overWritePolicy;
2135
2136 std::pair<std::string, std::string> redfishDateTimeOffset =
2137 redfish::time_utils::getDateTimeOffsetNow();
2138 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
2139 asyncResp->res.jsonValue["DateTimeLocalOffset"] =
2140 redfishDateTimeOffset.second;
2141
2142 asyncResp->res.jsonValue["Entries"]["@odata.id"] = dumpPath + "/Entries";
2143
2144 if (collectDiagnosticDataSupported)
2145 {
2146 asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"]
2147 ["target"] =
2148 dumpPath + "/Actions/LogService.CollectDiagnosticData";
2149 }
2150
2151 constexpr std::array<std::string_view, 1> interfaces = {deleteAllInterface};
2152 dbus::utility::getSubTreePaths(
2153 "/xyz/openbmc_project/dump", 0, interfaces,
2154 [asyncResp, dumpType, dumpPath](
2155 const boost::system::error_code& ec,
2156 const dbus::utility::MapperGetSubTreePathsResponse& subTreePaths) {
2157 if (ec)
2158 {
2159 BMCWEB_LOG_ERROR("getDumpServiceInfo respHandler got error {}",
2160 ec);
2161 // Assume that getting an error simply means there are no dump
2162 // LogServices. Return without adding any error response.
2163 return;
2164 }
2165 std::string dbusDumpPath = getDumpPath(dumpType);
2166 for (const std::string& path : subTreePaths)
2167 {
2168 if (path == dbusDumpPath)
2169 {
2170 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]
2171 ["target"] =
2172 dumpPath + "/Actions/LogService.ClearLog";
2173 break;
2174 }
2175 }
2176 });
2177 }
2178
handleLogServicesDumpServiceGet(crow::App & app,const std::string & dumpType,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)2179 inline void handleLogServicesDumpServiceGet(
2180 crow::App& app, const std::string& dumpType, const crow::Request& req,
2181 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2182 const std::string& managerId)
2183 {
2184 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2185 {
2186 return;
2187 }
2188
2189 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2190 {
2191 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2192 return;
2193 }
2194
2195 getDumpServiceInfo(asyncResp, dumpType);
2196 }
2197
handleLogServicesDumpServiceComputerSystemGet(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)2198 inline void handleLogServicesDumpServiceComputerSystemGet(
2199 crow::App& app, const crow::Request& req,
2200 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2201 const std::string& chassisId)
2202 {
2203 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2204 {
2205 return;
2206 }
2207 if (chassisId != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2208 {
2209 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
2210 return;
2211 }
2212 getDumpServiceInfo(asyncResp, "System");
2213 }
2214
handleLogServicesDumpEntriesCollectionGet(crow::App & app,const std::string & dumpType,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)2215 inline void handleLogServicesDumpEntriesCollectionGet(
2216 crow::App& app, const std::string& dumpType, const crow::Request& req,
2217 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2218 const std::string& managerId)
2219 {
2220 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2221 {
2222 return;
2223 }
2224
2225 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2226 {
2227 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2228 return;
2229 }
2230 getDumpEntryCollection(asyncResp, dumpType);
2231 }
2232
handleLogServicesDumpEntriesCollectionComputerSystemGet(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)2233 inline void handleLogServicesDumpEntriesCollectionComputerSystemGet(
2234 crow::App& app, const crow::Request& req,
2235 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2236 const std::string& chassisId)
2237 {
2238 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2239 {
2240 return;
2241 }
2242 if (chassisId != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2243 {
2244 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
2245 return;
2246 }
2247 getDumpEntryCollection(asyncResp, "System");
2248 }
2249
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)2250 inline void handleLogServicesDumpEntryGet(
2251 crow::App& app, const std::string& dumpType, const crow::Request& req,
2252 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2253 const std::string& managerId, const std::string& dumpId)
2254 {
2255 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2256 {
2257 return;
2258 }
2259 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2260 {
2261 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2262 return;
2263 }
2264 getDumpEntryById(asyncResp, dumpId, dumpType);
2265 }
2266
handleLogServicesDumpEntryComputerSystemGet(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & dumpId)2267 inline void handleLogServicesDumpEntryComputerSystemGet(
2268 crow::App& app, const crow::Request& req,
2269 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2270 const std::string& chassisId, const std::string& dumpId)
2271 {
2272 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2273 {
2274 return;
2275 }
2276 if (chassisId != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2277 {
2278 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
2279 return;
2280 }
2281 getDumpEntryById(asyncResp, dumpId, "System");
2282 }
2283
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)2284 inline void handleLogServicesDumpEntryDelete(
2285 crow::App& app, const std::string& dumpType, const crow::Request& req,
2286 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2287 const std::string& managerId, const std::string& dumpId)
2288 {
2289 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2290 {
2291 return;
2292 }
2293
2294 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2295 {
2296 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2297 return;
2298 }
2299 deleteDumpEntry(asyncResp, dumpId, dumpType);
2300 }
2301
handleLogServicesDumpEntryComputerSystemDelete(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & dumpId)2302 inline void handleLogServicesDumpEntryComputerSystemDelete(
2303 crow::App& app, const crow::Request& req,
2304 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2305 const std::string& chassisId, const std::string& dumpId)
2306 {
2307 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2308 {
2309 return;
2310 }
2311 if (chassisId != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2312 {
2313 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
2314 return;
2315 }
2316 deleteDumpEntry(asyncResp, dumpId, "System");
2317 }
2318
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)2319 inline void handleLogServicesDumpEntryDownloadGet(
2320 crow::App& app, const std::string& dumpType, const crow::Request& req,
2321 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2322 const std::string& managerId, const std::string& dumpId)
2323 {
2324 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2325 {
2326 return;
2327 }
2328
2329 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2330 {
2331 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2332 return;
2333 }
2334 downloadDumpEntry(asyncResp, dumpId, dumpType);
2335 }
2336
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)2337 inline void handleDBusEventLogEntryDownloadGet(
2338 crow::App& app, const std::string& dumpType, const crow::Request& req,
2339 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2340 const std::string& systemName, const std::string& entryID)
2341 {
2342 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2343 {
2344 return;
2345 }
2346 if (!http_helpers::isContentTypeAllowed(
2347 req.getHeaderValue("Accept"),
2348 http_helpers::ContentType::OctetStream, true))
2349 {
2350 asyncResp->res.result(boost::beast::http::status::bad_request);
2351 return;
2352 }
2353 downloadEventLogEntry(asyncResp, systemName, entryID, dumpType);
2354 }
2355
handleLogServicesDumpCollectDiagnosticDataPost(crow::App & app,const std::string & dumpType,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)2356 inline void handleLogServicesDumpCollectDiagnosticDataPost(
2357 crow::App& app, const std::string& dumpType, const crow::Request& req,
2358 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2359 const std::string& managerId)
2360 {
2361 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2362 {
2363 return;
2364 }
2365 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2366 {
2367 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2368 return;
2369 }
2370
2371 createDump(asyncResp, req, dumpType);
2372 }
2373
handleLogServicesDumpCollectDiagnosticDataComputerSystemPost(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName)2374 inline void handleLogServicesDumpCollectDiagnosticDataComputerSystemPost(
2375 crow::App& app, const crow::Request& req,
2376 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2377 const std::string& systemName)
2378 {
2379 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2380 {
2381 return;
2382 }
2383
2384 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2385 {
2386 // Option currently returns no systems. TBD
2387 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2388 systemName);
2389 return;
2390 }
2391 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2392 {
2393 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2394 systemName);
2395 return;
2396 }
2397 createDump(asyncResp, req, "System");
2398 }
2399
handleLogServicesDumpClearLogPost(crow::App & app,const std::string & dumpType,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)2400 inline void handleLogServicesDumpClearLogPost(
2401 crow::App& app, const std::string& dumpType, const crow::Request& req,
2402 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2403 const std::string& managerId)
2404 {
2405 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2406 {
2407 return;
2408 }
2409
2410 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2411 {
2412 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2413 return;
2414 }
2415 clearDump(asyncResp, dumpType);
2416 }
2417
handleLogServicesDumpClearLogComputerSystemPost(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName)2418 inline void handleLogServicesDumpClearLogComputerSystemPost(
2419 crow::App& app, const crow::Request& req,
2420 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2421 const std::string& systemName)
2422 {
2423 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2424 {
2425 return;
2426 }
2427 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2428 {
2429 // Option currently returns no systems. TBD
2430 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2431 systemName);
2432 return;
2433 }
2434 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2435 {
2436 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2437 systemName);
2438 return;
2439 }
2440 clearDump(asyncResp, "System");
2441 }
2442
requestRoutesBMCDumpService(App & app)2443 inline void requestRoutesBMCDumpService(App& app)
2444 {
2445 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Dump/")
2446 .privileges(redfish::privileges::getLogService)
2447 .methods(boost::beast::http::verb::get)(std::bind_front(
2448 handleLogServicesDumpServiceGet, std::ref(app), "BMC"));
2449 }
2450
requestRoutesBMCDumpEntryCollection(App & app)2451 inline void requestRoutesBMCDumpEntryCollection(App& app)
2452 {
2453 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Dump/Entries/")
2454 .privileges(redfish::privileges::getLogEntryCollection)
2455 .methods(boost::beast::http::verb::get)(std::bind_front(
2456 handleLogServicesDumpEntriesCollectionGet, std::ref(app), "BMC"));
2457 }
2458
requestRoutesBMCDumpEntry(App & app)2459 inline void requestRoutesBMCDumpEntry(App& app)
2460 {
2461 BMCWEB_ROUTE(app,
2462 "/redfish/v1/Managers/<str>/LogServices/Dump/Entries/<str>/")
2463 .privileges(redfish::privileges::getLogEntry)
2464 .methods(boost::beast::http::verb::get)(std::bind_front(
2465 handleLogServicesDumpEntryGet, std::ref(app), "BMC"));
2466
2467 BMCWEB_ROUTE(app,
2468 "/redfish/v1/Managers/<str>/LogServices/Dump/Entries/<str>/")
2469 .privileges(redfish::privileges::deleteLogEntry)
2470 .methods(boost::beast::http::verb::delete_)(std::bind_front(
2471 handleLogServicesDumpEntryDelete, std::ref(app), "BMC"));
2472 }
2473
requestRoutesBMCDumpEntryDownload(App & app)2474 inline void requestRoutesBMCDumpEntryDownload(App& app)
2475 {
2476 BMCWEB_ROUTE(
2477 app,
2478 "/redfish/v1/Managers/<str>/LogServices/Dump/Entries/<str>/attachment/")
2479 .privileges(redfish::privileges::getLogEntry)
2480 .methods(boost::beast::http::verb::get)(std::bind_front(
2481 handleLogServicesDumpEntryDownloadGet, std::ref(app), "BMC"));
2482 }
2483
requestRoutesBMCDumpCreate(App & app)2484 inline void requestRoutesBMCDumpCreate(App& app)
2485 {
2486 BMCWEB_ROUTE(
2487 app,
2488 "/redfish/v1/Managers/<str>/LogServices/Dump/Actions/LogService.CollectDiagnosticData/")
2489 .privileges(redfish::privileges::postLogService)
2490 .methods(boost::beast::http::verb::post)(
2491 std::bind_front(handleLogServicesDumpCollectDiagnosticDataPost,
2492 std::ref(app), "BMC"));
2493 }
2494
requestRoutesBMCDumpClear(App & app)2495 inline void requestRoutesBMCDumpClear(App& app)
2496 {
2497 BMCWEB_ROUTE(
2498 app,
2499 "/redfish/v1/Managers/<str>/LogServices/Dump/Actions/LogService.ClearLog/")
2500 .privileges(redfish::privileges::postLogService)
2501 .methods(boost::beast::http::verb::post)(std::bind_front(
2502 handleLogServicesDumpClearLogPost, std::ref(app), "BMC"));
2503 }
2504
requestRoutesDBusEventLogEntryDownload(App & app)2505 inline void requestRoutesDBusEventLogEntryDownload(App& app)
2506 {
2507 BMCWEB_ROUTE(
2508 app,
2509 "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/attachment/")
2510 .privileges(redfish::privileges::getLogEntry)
2511 .methods(boost::beast::http::verb::get)(std::bind_front(
2512 handleDBusEventLogEntryDownloadGet, std::ref(app), "System"));
2513 }
2514
requestRoutesFaultLogDumpService(App & app)2515 inline void requestRoutesFaultLogDumpService(App& app)
2516 {
2517 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/FaultLog/")
2518 .privileges(redfish::privileges::getLogService)
2519 .methods(boost::beast::http::verb::get)(std::bind_front(
2520 handleLogServicesDumpServiceGet, std::ref(app), "FaultLog"));
2521 }
2522
requestRoutesFaultLogDumpEntryCollection(App & app)2523 inline void requestRoutesFaultLogDumpEntryCollection(App& app)
2524 {
2525 BMCWEB_ROUTE(app,
2526 "/redfish/v1/Managers/<str>/LogServices/FaultLog/Entries/")
2527 .privileges(redfish::privileges::getLogEntryCollection)
2528 .methods(boost::beast::http::verb::get)(
2529 std::bind_front(handleLogServicesDumpEntriesCollectionGet,
2530 std::ref(app), "FaultLog"));
2531 }
2532
requestRoutesFaultLogDumpEntry(App & app)2533 inline void requestRoutesFaultLogDumpEntry(App& app)
2534 {
2535 BMCWEB_ROUTE(
2536 app, "/redfish/v1/Managers/<str>/LogServices/FaultLog/Entries/<str>/")
2537 .privileges(redfish::privileges::getLogEntry)
2538 .methods(boost::beast::http::verb::get)(std::bind_front(
2539 handleLogServicesDumpEntryGet, std::ref(app), "FaultLog"));
2540
2541 BMCWEB_ROUTE(
2542 app, "/redfish/v1/Managers/<str>/LogServices/FaultLog/Entries/<str>/")
2543 .privileges(redfish::privileges::deleteLogEntry)
2544 .methods(boost::beast::http::verb::delete_)(std::bind_front(
2545 handleLogServicesDumpEntryDelete, std::ref(app), "FaultLog"));
2546 }
2547
requestRoutesFaultLogDumpClear(App & app)2548 inline void requestRoutesFaultLogDumpClear(App& app)
2549 {
2550 BMCWEB_ROUTE(
2551 app,
2552 "/redfish/v1/Managers/<str>/LogServices/FaultLog/Actions/LogService.ClearLog/")
2553 .privileges(redfish::privileges::postLogService)
2554 .methods(boost::beast::http::verb::post)(std::bind_front(
2555 handleLogServicesDumpClearLogPost, std::ref(app), "FaultLog"));
2556 }
2557
requestRoutesSystemDumpService(App & app)2558 inline void requestRoutesSystemDumpService(App& app)
2559 {
2560 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Dump/")
2561 .privileges(redfish::privileges::getLogService)
2562 .methods(boost::beast::http::verb::get)(std::bind_front(
2563 handleLogServicesDumpServiceComputerSystemGet, std::ref(app)));
2564 }
2565
requestRoutesSystemDumpEntryCollection(App & app)2566 inline void requestRoutesSystemDumpEntryCollection(App& app)
2567 {
2568 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/")
2569 .privileges(redfish::privileges::getLogEntryCollection)
2570 .methods(boost::beast::http::verb::get)(std::bind_front(
2571 handleLogServicesDumpEntriesCollectionComputerSystemGet,
2572 std::ref(app)));
2573 }
2574
requestRoutesSystemDumpEntry(App & app)2575 inline void requestRoutesSystemDumpEntry(App& app)
2576 {
2577 BMCWEB_ROUTE(app,
2578 "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/<str>/")
2579 .privileges(redfish::privileges::getLogEntry)
2580 .methods(boost::beast::http::verb::get)(std::bind_front(
2581 handleLogServicesDumpEntryComputerSystemGet, std::ref(app)));
2582
2583 BMCWEB_ROUTE(app,
2584 "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/<str>/")
2585 .privileges(redfish::privileges::deleteLogEntry)
2586 .methods(boost::beast::http::verb::delete_)(std::bind_front(
2587 handleLogServicesDumpEntryComputerSystemDelete, std::ref(app)));
2588 }
2589
requestRoutesSystemDumpCreate(App & app)2590 inline void requestRoutesSystemDumpCreate(App& app)
2591 {
2592 BMCWEB_ROUTE(
2593 app,
2594 "/redfish/v1/Systems/<str>/LogServices/Dump/Actions/LogService.CollectDiagnosticData/")
2595 .privileges(redfish::privileges::postLogService)
2596 .methods(boost::beast::http::verb::post)(std::bind_front(
2597 handleLogServicesDumpCollectDiagnosticDataComputerSystemPost,
2598 std::ref(app)));
2599 }
2600
requestRoutesSystemDumpClear(App & app)2601 inline void requestRoutesSystemDumpClear(App& app)
2602 {
2603 BMCWEB_ROUTE(
2604 app,
2605 "/redfish/v1/Systems/<str>/LogServices/Dump/Actions/LogService.ClearLog/")
2606 .privileges(redfish::privileges::postLogService)
2607 .methods(boost::beast::http::verb::post)(std::bind_front(
2608 handleLogServicesDumpClearLogComputerSystemPost, std::ref(app)));
2609 }
2610
requestRoutesCrashdumpService(App & app)2611 inline void requestRoutesCrashdumpService(App& app)
2612 {
2613 // Note: Deviated from redfish privilege registry for GET & HEAD
2614 // method for security reasons.
2615 /**
2616 * Functions triggers appropriate requests on DBus
2617 */
2618 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Crashdump/")
2619 // This is incorrect, should be:
2620 //.privileges(redfish::privileges::getLogService)
2621 .privileges({{"ConfigureManager"}})
2622 .methods(
2623 boost::beast::http::verb::
2624 get)([&app](const crow::Request& req,
2625 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2626 const std::string& systemName) {
2627 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2628 {
2629 return;
2630 }
2631 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2632 {
2633 // Option currently returns no systems. TBD
2634 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2635 systemName);
2636 return;
2637 }
2638 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2639 {
2640 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2641 systemName);
2642 return;
2643 }
2644
2645 // Copy over the static data to include the entries added by
2646 // SubRoute
2647 asyncResp->res.jsonValue["@odata.id"] =
2648 std::format("/redfish/v1/Systems/{}/LogServices/Crashdump",
2649 BMCWEB_REDFISH_SYSTEM_URI_NAME);
2650 asyncResp->res.jsonValue["@odata.type"] =
2651 "#LogService.v1_2_0.LogService";
2652 asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service";
2653 asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service";
2654 asyncResp->res.jsonValue["Id"] = "Crashdump";
2655 asyncResp->res.jsonValue["OverWritePolicy"] =
2656 log_service::OverWritePolicy::WrapsWhenFull;
2657 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
2658
2659 std::pair<std::string, std::string> redfishDateTimeOffset =
2660 redfish::time_utils::getDateTimeOffsetNow();
2661 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
2662 asyncResp->res.jsonValue["DateTimeLocalOffset"] =
2663 redfishDateTimeOffset.second;
2664
2665 asyncResp->res.jsonValue["Entries"]["@odata.id"] = std::format(
2666 "/redfish/v1/Systems/{}/LogServices/Crashdump/Entries",
2667 BMCWEB_REDFISH_SYSTEM_URI_NAME);
2668 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]
2669 ["target"] = std::format(
2670 "/redfish/v1/Systems/{}/LogServices/Crashdump/Actions/LogService.ClearLog",
2671 BMCWEB_REDFISH_SYSTEM_URI_NAME);
2672 asyncResp->res
2673 .jsonValue["Actions"]["#LogService.CollectDiagnosticData"]
2674 ["target"] = std::format(
2675 "/redfish/v1/Systems/{}/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData",
2676 BMCWEB_REDFISH_SYSTEM_URI_NAME);
2677 });
2678 }
2679
requestRoutesCrashdumpClear(App & app)2680 void inline requestRoutesCrashdumpClear(App& app)
2681 {
2682 BMCWEB_ROUTE(
2683 app,
2684 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Actions/LogService.ClearLog/")
2685 // This is incorrect, should be:
2686 //.privileges(redfish::privileges::postLogService)
2687 .privileges({{"ConfigureComponents"}})
2688 .methods(boost::beast::http::verb::post)(
2689 [&app](const crow::Request& req,
2690 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2691 const std::string& systemName) {
2692 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2693 {
2694 return;
2695 }
2696 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2697 {
2698 // Option currently returns no systems. TBD
2699 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2700 systemName);
2701 return;
2702 }
2703 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2704 {
2705 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2706 systemName);
2707 return;
2708 }
2709 crow::connections::systemBus->async_method_call(
2710 [asyncResp](const boost::system::error_code& ec,
2711 const std::string&) {
2712 if (ec)
2713 {
2714 messages::internalError(asyncResp->res);
2715 return;
2716 }
2717 messages::success(asyncResp->res);
2718 },
2719 crashdumpObject, crashdumpPath, deleteAllInterface,
2720 "DeleteAll");
2721 });
2722 }
2723
2724 inline void
logCrashdumpEntry(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & logID,nlohmann::json & logEntryJson)2725 logCrashdumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2726 const std::string& logID, nlohmann::json& logEntryJson)
2727 {
2728 auto getStoredLogCallback =
2729 [asyncResp, logID,
2730 &logEntryJson](const boost::system::error_code& ec,
2731 const dbus::utility::DBusPropertiesMap& params) {
2732 if (ec)
2733 {
2734 BMCWEB_LOG_DEBUG("failed to get log ec: {}", ec.message());
2735 if (ec.value() ==
2736 boost::system::linux_error::bad_request_descriptor)
2737 {
2738 messages::resourceNotFound(asyncResp->res, "LogEntry",
2739 logID);
2740 }
2741 else
2742 {
2743 messages::internalError(asyncResp->res);
2744 }
2745 return;
2746 }
2747
2748 std::string timestamp{};
2749 std::string filename{};
2750 std::string logfile{};
2751 parseCrashdumpParameters(params, filename, timestamp, logfile);
2752
2753 if (filename.empty() || timestamp.empty())
2754 {
2755 messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
2756 return;
2757 }
2758
2759 std::string crashdumpURI =
2760 std::format(
2761 "/redfish/v1/Systems/{}/LogServices/Crashdump/Entries/",
2762 BMCWEB_REDFISH_SYSTEM_URI_NAME) +
2763 logID + "/" + filename;
2764 nlohmann::json::object_t logEntry;
2765 logEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
2766 logEntry["@odata.id"] = boost::urls::format(
2767 "/redfish/v1/Systems/{}/LogServices/Crashdump/Entries/{}",
2768 BMCWEB_REDFISH_SYSTEM_URI_NAME, logID);
2769 logEntry["Name"] = "CPU Crashdump";
2770 logEntry["Id"] = logID;
2771 logEntry["EntryType"] = log_entry::LogEntryType::Oem;
2772 logEntry["AdditionalDataURI"] = std::move(crashdumpURI);
2773 logEntry["DiagnosticDataType"] = "OEM";
2774 logEntry["OEMDiagnosticDataType"] = "PECICrashdump";
2775 logEntry["Created"] = std::move(timestamp);
2776
2777 // If logEntryJson references an array of LogEntry resources
2778 // ('Members' list), then push this as a new entry, otherwise set it
2779 // directly
2780 if (logEntryJson.is_array())
2781 {
2782 logEntryJson.push_back(logEntry);
2783 asyncResp->res.jsonValue["Members@odata.count"] =
2784 logEntryJson.size();
2785 }
2786 else
2787 {
2788 logEntryJson.update(logEntry);
2789 }
2790 };
2791 sdbusplus::asio::getAllProperties(
2792 *crow::connections::systemBus, crashdumpObject,
2793 crashdumpPath + std::string("/") + logID, crashdumpInterface,
2794 std::move(getStoredLogCallback));
2795 }
2796
requestRoutesCrashdumpEntryCollection(App & app)2797 inline void requestRoutesCrashdumpEntryCollection(App& app)
2798 {
2799 // Note: Deviated from redfish privilege registry for GET & HEAD
2800 // method for security reasons.
2801 /**
2802 * Functions triggers appropriate requests on DBus
2803 */
2804 BMCWEB_ROUTE(app,
2805 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/")
2806 // This is incorrect, should be.
2807 //.privileges(redfish::privileges::postLogEntryCollection)
2808 .privileges({{"ConfigureComponents"}})
2809 .methods(
2810 boost::beast::http::verb::
2811 get)([&app](const crow::Request& req,
2812 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2813 const std::string& systemName) {
2814 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2815 {
2816 return;
2817 }
2818 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2819 {
2820 // Option currently returns no systems. TBD
2821 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2822 systemName);
2823 return;
2824 }
2825 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2826 {
2827 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2828 systemName);
2829 return;
2830 }
2831
2832 constexpr std::array<std::string_view, 1> interfaces = {
2833 crashdumpInterface};
2834 dbus::utility::getSubTreePaths(
2835 "/", 0, interfaces,
2836 [asyncResp](const boost::system::error_code& ec,
2837 const std::vector<std::string>& resp) {
2838 if (ec)
2839 {
2840 if (ec.value() !=
2841 boost::system::errc::no_such_file_or_directory)
2842 {
2843 BMCWEB_LOG_DEBUG("failed to get entries ec: {}",
2844 ec.message());
2845 messages::internalError(asyncResp->res);
2846 return;
2847 }
2848 }
2849 asyncResp->res.jsonValue["@odata.type"] =
2850 "#LogEntryCollection.LogEntryCollection";
2851 asyncResp->res.jsonValue["@odata.id"] = std::format(
2852 "/redfish/v1/Systems/{}/LogServices/Crashdump/Entries",
2853 BMCWEB_REDFISH_SYSTEM_URI_NAME);
2854 asyncResp->res.jsonValue["Name"] =
2855 "Open BMC Crashdump Entries";
2856 asyncResp->res.jsonValue["Description"] =
2857 "Collection of Crashdump Entries";
2858 asyncResp->res.jsonValue["Members"] =
2859 nlohmann::json::array();
2860 asyncResp->res.jsonValue["Members@odata.count"] = 0;
2861
2862 for (const std::string& path : resp)
2863 {
2864 const sdbusplus::message::object_path objPath(path);
2865 // Get the log ID
2866 std::string logID = objPath.filename();
2867 if (logID.empty())
2868 {
2869 continue;
2870 }
2871 // Add the log entry to the array
2872 logCrashdumpEntry(asyncResp, logID,
2873 asyncResp->res.jsonValue["Members"]);
2874 }
2875 });
2876 });
2877 }
2878
requestRoutesCrashdumpEntry(App & app)2879 inline void requestRoutesCrashdumpEntry(App& app)
2880 {
2881 // Note: Deviated from redfish privilege registry for GET & HEAD
2882 // method for security reasons.
2883
2884 BMCWEB_ROUTE(
2885 app, "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/<str>/")
2886 // this is incorrect, should be
2887 // .privileges(redfish::privileges::getLogEntry)
2888 .privileges({{"ConfigureComponents"}})
2889 .methods(boost::beast::http::verb::get)(
2890 [&app](const crow::Request& req,
2891 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2892 const std::string& systemName, const std::string& param) {
2893 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2894 {
2895 return;
2896 }
2897 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2898 {
2899 // Option currently returns no systems. TBD
2900 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2901 systemName);
2902 return;
2903 }
2904 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2905 {
2906 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2907 systemName);
2908 return;
2909 }
2910 const std::string& logID = param;
2911 logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue);
2912 });
2913 }
2914
requestRoutesCrashdumpFile(App & app)2915 inline void requestRoutesCrashdumpFile(App& app)
2916 {
2917 // Note: Deviated from redfish privilege registry for GET & HEAD
2918 // method for security reasons.
2919 BMCWEB_ROUTE(
2920 app,
2921 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/<str>/<str>/")
2922 .privileges(redfish::privileges::getLogEntry)
2923 .methods(boost::beast::http::verb::get)(
2924 [](const crow::Request& req,
2925 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2926 const std::string& systemName, const std::string& logID,
2927 const std::string& fileName) {
2928 // Do not call getRedfishRoute here since the crashdump file is
2929 // not a Redfish resource.
2930
2931 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2932 {
2933 // Option currently returns no systems. TBD
2934 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2935 systemName);
2936 return;
2937 }
2938 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2939 {
2940 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2941 systemName);
2942 return;
2943 }
2944
2945 auto getStoredLogCallback =
2946 [asyncResp, logID, fileName,
2947 url(boost::urls::url(req.url()))](
2948 const boost::system::error_code& ec,
2949 const std::vector<std::pair<
2950 std::string, dbus::utility::DbusVariantType>>&
2951 resp) {
2952 if (ec)
2953 {
2954 BMCWEB_LOG_DEBUG("failed to get log ec: {}",
2955 ec.message());
2956 messages::internalError(asyncResp->res);
2957 return;
2958 }
2959
2960 std::string dbusFilename{};
2961 std::string dbusTimestamp{};
2962 std::string dbusFilepath{};
2963
2964 parseCrashdumpParameters(resp, dbusFilename,
2965 dbusTimestamp, dbusFilepath);
2966
2967 if (dbusFilename.empty() || dbusTimestamp.empty() ||
2968 dbusFilepath.empty())
2969 {
2970 messages::resourceNotFound(asyncResp->res,
2971 "LogEntry", logID);
2972 return;
2973 }
2974
2975 // Verify the file name parameter is correct
2976 if (fileName != dbusFilename)
2977 {
2978 messages::resourceNotFound(asyncResp->res,
2979 "LogEntry", logID);
2980 return;
2981 }
2982
2983 if (asyncResp->res.openFile(dbusFilepath) !=
2984 crow::OpenCode::Success)
2985 {
2986 messages::resourceNotFound(asyncResp->res,
2987 "LogEntry", logID);
2988 return;
2989 }
2990
2991 // Configure this to be a file download when accessed
2992 // from a browser
2993 asyncResp->res.addHeader(
2994 boost::beast::http::field::content_disposition,
2995 "attachment");
2996 };
2997 sdbusplus::asio::getAllProperties(
2998 *crow::connections::systemBus, crashdumpObject,
2999 crashdumpPath + std::string("/") + logID,
3000 crashdumpInterface, std::move(getStoredLogCallback));
3001 });
3002 }
3003
3004 enum class OEMDiagnosticType
3005 {
3006 onDemand,
3007 telemetry,
3008 invalid,
3009 };
3010
getOEMDiagnosticType(std::string_view oemDiagStr)3011 inline OEMDiagnosticType getOEMDiagnosticType(std::string_view oemDiagStr)
3012 {
3013 if (oemDiagStr == "OnDemand")
3014 {
3015 return OEMDiagnosticType::onDemand;
3016 }
3017 if (oemDiagStr == "Telemetry")
3018 {
3019 return OEMDiagnosticType::telemetry;
3020 }
3021
3022 return OEMDiagnosticType::invalid;
3023 }
3024
requestRoutesCrashdumpCollect(App & app)3025 inline void requestRoutesCrashdumpCollect(App& app)
3026 {
3027 // Note: Deviated from redfish privilege registry for GET & HEAD
3028 // method for security reasons.
3029 BMCWEB_ROUTE(
3030 app,
3031 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData/")
3032 // The below is incorrect; Should be ConfigureManager
3033 //.privileges(redfish::privileges::postLogService)
3034 .privileges({{"ConfigureComponents"}})
3035 .methods(boost::beast::http::verb::post)(
3036 [&app](const crow::Request& req,
3037 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3038 const std::string& systemName) {
3039 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3040 {
3041 return;
3042 }
3043
3044 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
3045 {
3046 // Option currently returns no systems. TBD
3047 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3048 systemName);
3049 return;
3050 }
3051 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
3052 {
3053 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3054 systemName);
3055 return;
3056 }
3057
3058 std::string diagnosticDataType;
3059 std::string oemDiagnosticDataType;
3060 if (!redfish::json_util::readJsonAction( //
3061 req, asyncResp->res, //
3062 "DiagnosticDataType", diagnosticDataType, //
3063 "OEMDiagnosticDataType", oemDiagnosticDataType //
3064 ))
3065 {
3066 return;
3067 }
3068
3069 if (diagnosticDataType != "OEM")
3070 {
3071 BMCWEB_LOG_ERROR(
3072 "Only OEM DiagnosticDataType supported for Crashdump");
3073 messages::actionParameterValueFormatError(
3074 asyncResp->res, diagnosticDataType,
3075 "DiagnosticDataType", "CollectDiagnosticData");
3076 return;
3077 }
3078
3079 OEMDiagnosticType oemDiagType =
3080 getOEMDiagnosticType(oemDiagnosticDataType);
3081
3082 std::string iface;
3083 std::string method;
3084 std::string taskMatchStr;
3085 if (oemDiagType == OEMDiagnosticType::onDemand)
3086 {
3087 iface = crashdumpOnDemandInterface;
3088 method = "GenerateOnDemandLog";
3089 taskMatchStr =
3090 "type='signal',"
3091 "interface='org.freedesktop.DBus.Properties',"
3092 "member='PropertiesChanged',"
3093 "arg0namespace='com.intel.crashdump'";
3094 }
3095 else if (oemDiagType == OEMDiagnosticType::telemetry)
3096 {
3097 iface = crashdumpTelemetryInterface;
3098 method = "GenerateTelemetryLog";
3099 taskMatchStr =
3100 "type='signal',"
3101 "interface='org.freedesktop.DBus.Properties',"
3102 "member='PropertiesChanged',"
3103 "arg0namespace='com.intel.crashdump'";
3104 }
3105 else
3106 {
3107 BMCWEB_LOG_ERROR("Unsupported OEMDiagnosticDataType: {}",
3108 oemDiagnosticDataType);
3109 messages::actionParameterValueFormatError(
3110 asyncResp->res, oemDiagnosticDataType,
3111 "OEMDiagnosticDataType", "CollectDiagnosticData");
3112 return;
3113 }
3114
3115 auto collectCrashdumpCallback =
3116 [asyncResp, payload(task::Payload(req)),
3117 taskMatchStr](const boost::system::error_code& ec,
3118 const std::string&) mutable {
3119 if (ec)
3120 {
3121 if (ec.value() ==
3122 boost::system::errc::operation_not_supported)
3123 {
3124 messages::resourceInStandby(asyncResp->res);
3125 }
3126 else if (ec.value() == boost::system::errc::
3127 device_or_resource_busy)
3128 {
3129 messages::serviceTemporarilyUnavailable(
3130 asyncResp->res, "60");
3131 }
3132 else
3133 {
3134 messages::internalError(asyncResp->res);
3135 }
3136 return;
3137 }
3138 std::shared_ptr<task::TaskData> task =
3139 task::TaskData::createTask(
3140 [](const boost::system::error_code& ec2,
3141 sdbusplus::message_t&,
3142 const std::shared_ptr<task::TaskData>&
3143 taskData) {
3144 if (!ec2)
3145 {
3146 taskData->messages.emplace_back(
3147 messages::taskCompletedOK(
3148 std::to_string(
3149 taskData->index)));
3150 taskData->state = "Completed";
3151 }
3152 return task::completed;
3153 },
3154 taskMatchStr);
3155
3156 task->startTimer(std::chrono::minutes(5));
3157 task->populateResp(asyncResp->res);
3158 task->payload.emplace(std::move(payload));
3159 };
3160
3161 crow::connections::systemBus->async_method_call(
3162 std::move(collectCrashdumpCallback), crashdumpObject,
3163 crashdumpPath, iface, method);
3164 });
3165 }
3166
dBusLogServiceActionsClear(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)3167 inline void dBusLogServiceActionsClear(
3168 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
3169 {
3170 BMCWEB_LOG_DEBUG("Do delete all entries.");
3171
3172 // Process response from Logging service.
3173 auto respHandler = [asyncResp](const boost::system::error_code& ec) {
3174 BMCWEB_LOG_DEBUG("doClearLog resp_handler callback: Done");
3175 if (ec)
3176 {
3177 // TODO Handle for specific error code
3178 BMCWEB_LOG_ERROR("doClearLog resp_handler got error {}", ec);
3179 asyncResp->res.result(
3180 boost::beast::http::status::internal_server_error);
3181 return;
3182 }
3183
3184 asyncResp->res.result(boost::beast::http::status::no_content);
3185 };
3186
3187 // Make call to Logging service to request Clear Log
3188 crow::connections::systemBus->async_method_call(
3189 respHandler, "xyz.openbmc_project.Logging",
3190 "/xyz/openbmc_project/logging",
3191 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
3192 }
3193
3194 /**
3195 * DBusLogServiceActionsClear class supports POST method for ClearLog action.
3196 */
requestRoutesDBusLogServiceActionsClear(App & app)3197 inline void requestRoutesDBusLogServiceActionsClear(App& app)
3198 {
3199 /**
3200 * Function handles POST method request.
3201 * The Clear Log actions does not require any parameter.The action deletes
3202 * all entries found in the Entries collection for this Log Service.
3203 */
3204
3205 BMCWEB_ROUTE(
3206 app,
3207 "/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/")
3208 .privileges(redfish::privileges::postLogService)
3209 .methods(boost::beast::http::verb::post)(
3210 [&app](const crow::Request& req,
3211 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3212 const std::string& systemName) {
3213 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3214 {
3215 return;
3216 }
3217 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
3218 {
3219 // Option currently returns no systems. TBD
3220 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3221 systemName);
3222 return;
3223 }
3224 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
3225 {
3226 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3227 systemName);
3228 return;
3229 }
3230 dBusLogServiceActionsClear(asyncResp);
3231 });
3232 }
3233
3234 } // namespace redfish
3235