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, "DiagnosticDataType", diagnosticDataType,
946 "OEMDiagnosticDataType", oemDiagnosticDataType))
947 {
948 return;
949 }
950
951 if (dumpType == "System")
952 {
953 if (!oemDiagnosticDataType || !diagnosticDataType)
954 {
955 BMCWEB_LOG_ERROR(
956 "CreateDump action parameter 'DiagnosticDataType'/'OEMDiagnosticDataType' value not found!");
957 messages::actionParameterMissing(
958 asyncResp->res, "CollectDiagnosticData",
959 "DiagnosticDataType & OEMDiagnosticDataType");
960 return;
961 }
962 if ((*oemDiagnosticDataType != "System") ||
963 (*diagnosticDataType != "OEM"))
964 {
965 BMCWEB_LOG_ERROR("Wrong parameter values passed");
966 messages::internalError(asyncResp->res);
967 return;
968 }
969 dumpPath = std::format("/redfish/v1/Systems/{}/LogServices/Dump/",
970 BMCWEB_REDFISH_SYSTEM_URI_NAME);
971 }
972 else if (dumpType == "BMC")
973 {
974 if (!diagnosticDataType)
975 {
976 BMCWEB_LOG_ERROR(
977 "CreateDump action parameter 'DiagnosticDataType' not found!");
978 messages::actionParameterMissing(
979 asyncResp->res, "CollectDiagnosticData", "DiagnosticDataType");
980 return;
981 }
982 if (*diagnosticDataType != "Manager")
983 {
984 BMCWEB_LOG_ERROR(
985 "Wrong parameter value passed for 'DiagnosticDataType'");
986 messages::internalError(asyncResp->res);
987 return;
988 }
989 dumpPath = std::format("/redfish/v1/Managers/{}/LogServices/Dump/",
990 BMCWEB_REDFISH_MANAGER_URI_NAME);
991 }
992 else
993 {
994 BMCWEB_LOG_ERROR("CreateDump failed. Unknown dump type");
995 messages::internalError(asyncResp->res);
996 return;
997 }
998
999 std::vector<std::pair<std::string, std::variant<std::string, uint64_t>>>
1000 createDumpParamVec;
1001
1002 if (req.session != nullptr)
1003 {
1004 createDumpParamVec.emplace_back(
1005 "xyz.openbmc_project.Dump.Create.CreateParameters.OriginatorId",
1006 req.session->clientIp);
1007 createDumpParamVec.emplace_back(
1008 "xyz.openbmc_project.Dump.Create.CreateParameters.OriginatorType",
1009 "xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.Client");
1010 }
1011
1012 crow::connections::systemBus->async_method_call(
1013 [asyncResp, payload(task::Payload(req)),
1014 dumpPath](const boost::system::error_code& ec,
1015 const sdbusplus::message_t& msg,
1016 const sdbusplus::message::object_path& objPath) mutable {
1017 if (ec)
1018 {
1019 BMCWEB_LOG_ERROR("CreateDump resp_handler got error {}", ec);
1020 const sd_bus_error* dbusError = msg.get_error();
1021 if (dbusError == nullptr)
1022 {
1023 messages::internalError(asyncResp->res);
1024 return;
1025 }
1026
1027 BMCWEB_LOG_ERROR("CreateDump DBus error: {} and error msg: {}",
1028 dbusError->name, dbusError->message);
1029 if (std::string_view(
1030 "xyz.openbmc_project.Common.Error.NotAllowed") ==
1031 dbusError->name)
1032 {
1033 messages::resourceInStandby(asyncResp->res);
1034 return;
1035 }
1036 if (std::string_view(
1037 "xyz.openbmc_project.Dump.Create.Error.Disabled") ==
1038 dbusError->name)
1039 {
1040 messages::serviceDisabled(asyncResp->res, dumpPath);
1041 return;
1042 }
1043 if (std::string_view(
1044 "xyz.openbmc_project.Common.Error.Unavailable") ==
1045 dbusError->name)
1046 {
1047 messages::resourceInUse(asyncResp->res);
1048 return;
1049 }
1050 // Other Dbus errors such as:
1051 // xyz.openbmc_project.Common.Error.InvalidArgument &
1052 // org.freedesktop.DBus.Error.InvalidArgs are all related to
1053 // the dbus call that is made here in the bmcweb
1054 // implementation and has nothing to do with the client's
1055 // input in the request. Hence, returning internal error
1056 // back to the client.
1057 messages::internalError(asyncResp->res);
1058 return;
1059 }
1060 BMCWEB_LOG_DEBUG("Dump Created. Path: {}", objPath.str);
1061 createDumpTaskCallback(std::move(payload), asyncResp, objPath);
1062 },
1063 "xyz.openbmc_project.Dump.Manager", getDumpPath(dumpType),
1064 "xyz.openbmc_project.Dump.Create", "CreateDump", createDumpParamVec);
1065 }
1066
clearDump(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & dumpType)1067 inline void clearDump(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1068 const std::string& dumpType)
1069 {
1070 crow::connections::systemBus->async_method_call(
1071 [asyncResp](const boost::system::error_code& ec) {
1072 if (ec)
1073 {
1074 BMCWEB_LOG_ERROR("clearDump resp_handler got error {}", ec);
1075 messages::internalError(asyncResp->res);
1076 return;
1077 }
1078 },
1079 "xyz.openbmc_project.Dump.Manager", getDumpPath(dumpType),
1080 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
1081 }
1082
parseCrashdumpParameters(const dbus::utility::DBusPropertiesMap & params,std::string & filename,std::string & timestamp,std::string & logfile)1083 inline void parseCrashdumpParameters(
1084 const dbus::utility::DBusPropertiesMap& params, std::string& filename,
1085 std::string& timestamp, std::string& logfile)
1086 {
1087 const std::string* filenamePtr = nullptr;
1088 const std::string* timestampPtr = nullptr;
1089 const std::string* logfilePtr = nullptr;
1090
1091 const bool success = sdbusplus::unpackPropertiesNoThrow(
1092 dbus_utils::UnpackErrorPrinter(), params, "Timestamp", timestampPtr,
1093 "Filename", filenamePtr, "Log", logfilePtr);
1094
1095 if (!success)
1096 {
1097 return;
1098 }
1099
1100 if (filenamePtr != nullptr)
1101 {
1102 filename = *filenamePtr;
1103 }
1104
1105 if (timestampPtr != nullptr)
1106 {
1107 timestamp = *timestampPtr;
1108 }
1109
1110 if (logfilePtr != nullptr)
1111 {
1112 logfile = *logfilePtr;
1113 }
1114 }
1115
requestRoutesSystemLogServiceCollection(App & app)1116 inline void requestRoutesSystemLogServiceCollection(App& app)
1117 {
1118 /**
1119 * Functions triggers appropriate requests on DBus
1120 */
1121 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/")
1122 .privileges(redfish::privileges::getLogServiceCollection)
1123 .methods(
1124 boost::beast::http::verb::
1125 get)([&app](const crow::Request& req,
1126 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1127 const std::string& systemName) {
1128 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1129 {
1130 return;
1131 }
1132 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1133 {
1134 // Option currently returns no systems. TBD
1135 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1136 systemName);
1137 return;
1138 }
1139 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1140 {
1141 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1142 systemName);
1143 return;
1144 }
1145
1146 // Collections don't include the static data added by SubRoute
1147 // because it has a duplicate entry for members
1148 asyncResp->res.jsonValue["@odata.type"] =
1149 "#LogServiceCollection.LogServiceCollection";
1150 asyncResp->res.jsonValue["@odata.id"] =
1151 std::format("/redfish/v1/Systems/{}/LogServices",
1152 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1153 asyncResp->res.jsonValue["Name"] = "System Log Services Collection";
1154 asyncResp->res.jsonValue["Description"] =
1155 "Collection of LogServices for this Computer System";
1156 nlohmann::json& logServiceArray =
1157 asyncResp->res.jsonValue["Members"];
1158 logServiceArray = nlohmann::json::array();
1159 nlohmann::json::object_t eventLog;
1160 eventLog["@odata.id"] =
1161 std::format("/redfish/v1/Systems/{}/LogServices/EventLog",
1162 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1163 logServiceArray.emplace_back(std::move(eventLog));
1164 if constexpr (BMCWEB_REDFISH_DUMP_LOG)
1165 {
1166 nlohmann::json::object_t dumpLog;
1167 dumpLog["@odata.id"] =
1168 std::format("/redfish/v1/Systems/{}/LogServices/Dump",
1169 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1170 logServiceArray.emplace_back(std::move(dumpLog));
1171 }
1172
1173 if constexpr (BMCWEB_REDFISH_CPU_LOG)
1174 {
1175 nlohmann::json::object_t crashdump;
1176 crashdump["@odata.id"] =
1177 std::format("/redfish/v1/Systems/{}/LogServices/Crashdump",
1178 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1179 logServiceArray.emplace_back(std::move(crashdump));
1180 }
1181
1182 if constexpr (BMCWEB_REDFISH_HOST_LOGGER)
1183 {
1184 nlohmann::json::object_t hostlogger;
1185 hostlogger["@odata.id"] =
1186 std::format("/redfish/v1/Systems/{}/LogServices/HostLogger",
1187 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1188 logServiceArray.emplace_back(std::move(hostlogger));
1189 }
1190 asyncResp->res.jsonValue["Members@odata.count"] =
1191 logServiceArray.size();
1192
1193 constexpr std::array<std::string_view, 1> interfaces = {
1194 "xyz.openbmc_project.State.Boot.PostCode"};
1195 dbus::utility::getSubTreePaths(
1196 "/", 0, interfaces,
1197 [asyncResp](const boost::system::error_code& ec,
1198 const dbus::utility::MapperGetSubTreePathsResponse&
1199 subtreePath) {
1200 if (ec)
1201 {
1202 BMCWEB_LOG_ERROR("{}", ec);
1203 return;
1204 }
1205
1206 for (const auto& pathStr : subtreePath)
1207 {
1208 if (pathStr.find("PostCode") != std::string::npos)
1209 {
1210 nlohmann::json& logServiceArrayLocal =
1211 asyncResp->res.jsonValue["Members"];
1212 nlohmann::json::object_t member;
1213 member["@odata.id"] = std::format(
1214 "/redfish/v1/Systems/{}/LogServices/PostCodes",
1215 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1216
1217 logServiceArrayLocal.emplace_back(
1218 std::move(member));
1219
1220 asyncResp->res.jsonValue["Members@odata.count"] =
1221 logServiceArrayLocal.size();
1222 return;
1223 }
1224 }
1225 });
1226 });
1227 }
1228
requestRoutesEventLogService(App & app)1229 inline void requestRoutesEventLogService(App& app)
1230 {
1231 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/")
1232 .privileges(redfish::privileges::getLogService)
1233 .methods(
1234 boost::beast::http::verb::
1235 get)([&app](const crow::Request& req,
1236 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1237 const std::string& systemName) {
1238 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1239 {
1240 return;
1241 }
1242 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1243 {
1244 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1245 systemName);
1246 return;
1247 }
1248 asyncResp->res.jsonValue["@odata.id"] =
1249 std::format("/redfish/v1/Systems/{}/LogServices/EventLog",
1250 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1251 asyncResp->res.jsonValue["@odata.type"] =
1252 "#LogService.v1_2_0.LogService";
1253 asyncResp->res.jsonValue["Name"] = "Event Log Service";
1254 asyncResp->res.jsonValue["Description"] =
1255 "System Event Log Service";
1256 asyncResp->res.jsonValue["Id"] = "EventLog";
1257 asyncResp->res.jsonValue["OverWritePolicy"] =
1258 log_service::OverWritePolicy::WrapsWhenFull;
1259
1260 std::pair<std::string, std::string> redfishDateTimeOffset =
1261 redfish::time_utils::getDateTimeOffsetNow();
1262
1263 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
1264 asyncResp->res.jsonValue["DateTimeLocalOffset"] =
1265 redfishDateTimeOffset.second;
1266
1267 asyncResp->res.jsonValue["Entries"]["@odata.id"] = std::format(
1268 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries",
1269 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1270 asyncResp->res
1271 .jsonValue["Actions"]["#LogService.ClearLog"]["target"]
1272
1273 = std::format(
1274 "/redfish/v1/Systems/{}/LogServices/EventLog/Actions/LogService.ClearLog",
1275 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1276 });
1277 }
1278
handleSystemsLogServicesEventLogActionsClearPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName)1279 inline void handleSystemsLogServicesEventLogActionsClearPost(
1280 App& app, const crow::Request& req,
1281 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1282 const std::string& systemName)
1283 {
1284 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1285 {
1286 return;
1287 }
1288 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1289 {
1290 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1291 systemName);
1292 return;
1293 }
1294
1295 // Clear the EventLog by deleting the log files
1296 std::vector<std::filesystem::path> redfishLogFiles;
1297 if (getRedfishLogFiles(redfishLogFiles))
1298 {
1299 for (const std::filesystem::path& file : redfishLogFiles)
1300 {
1301 std::error_code ec;
1302 std::filesystem::remove(file, ec);
1303 }
1304 }
1305
1306 // Reload rsyslog so it knows to start new log files
1307 crow::connections::systemBus->async_method_call(
1308 [asyncResp](const boost::system::error_code& ec) {
1309 if (ec)
1310 {
1311 BMCWEB_LOG_ERROR("Failed to reload rsyslog: {}", ec);
1312 messages::internalError(asyncResp->res);
1313 return;
1314 }
1315
1316 messages::success(asyncResp->res);
1317 },
1318 "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
1319 "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service",
1320 "replace");
1321 }
1322
requestRoutesJournalEventLogClear(App & app)1323 inline void requestRoutesJournalEventLogClear(App& app)
1324 {
1325 BMCWEB_ROUTE(
1326 app,
1327 "/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/")
1328 .privileges({{"ConfigureComponents"}})
1329 .methods(boost::beast::http::verb::post)(std::bind_front(
1330 handleSystemsLogServicesEventLogActionsClearPost, std::ref(app)));
1331 }
1332
1333 enum class LogParseError
1334 {
1335 success,
1336 parseFailed,
1337 messageIdNotInRegistry,
1338 };
1339
fillEventLogEntryJson(const std::string & logEntryID,const std::string & logEntry,nlohmann::json::object_t & logEntryJson)1340 static LogParseError fillEventLogEntryJson(
1341 const std::string& logEntryID, const std::string& logEntry,
1342 nlohmann::json::object_t& logEntryJson)
1343 {
1344 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
1345 // First get the Timestamp
1346 size_t space = logEntry.find_first_of(' ');
1347 if (space == std::string::npos)
1348 {
1349 return LogParseError::parseFailed;
1350 }
1351 std::string timestamp = logEntry.substr(0, space);
1352 // Then get the log contents
1353 size_t entryStart = logEntry.find_first_not_of(' ', space);
1354 if (entryStart == std::string::npos)
1355 {
1356 return LogParseError::parseFailed;
1357 }
1358 std::string_view entry(logEntry);
1359 entry.remove_prefix(entryStart);
1360 // Use split to separate the entry into its fields
1361 std::vector<std::string> logEntryFields;
1362 bmcweb::split(logEntryFields, entry, ',');
1363 // We need at least a MessageId to be valid
1364 auto logEntryIter = logEntryFields.begin();
1365 if (logEntryIter == logEntryFields.end())
1366 {
1367 return LogParseError::parseFailed;
1368 }
1369 std::string& messageID = *logEntryIter;
1370 // Get the Message from the MessageRegistry
1371 const registries::Message* message = registries::getMessage(messageID);
1372
1373 logEntryIter++;
1374 if (message == nullptr)
1375 {
1376 BMCWEB_LOG_WARNING("Log entry not found in registry: {}", logEntry);
1377 return LogParseError::messageIdNotInRegistry;
1378 }
1379
1380 std::vector<std::string_view> messageArgs(logEntryIter,
1381 logEntryFields.end());
1382 messageArgs.resize(message->numberOfArgs);
1383
1384 std::string msg =
1385 redfish::registries::fillMessageArgs(messageArgs, message->message);
1386 if (msg.empty())
1387 {
1388 return LogParseError::parseFailed;
1389 }
1390
1391 // Get the Created time from the timestamp. The log timestamp is in RFC3339
1392 // format which matches the Redfish format except for the fractional seconds
1393 // between the '.' and the '+', so just remove them.
1394 std::size_t dot = timestamp.find_first_of('.');
1395 std::size_t plus = timestamp.find_first_of('+');
1396 if (dot != std::string::npos && plus != std::string::npos)
1397 {
1398 timestamp.erase(dot, plus - dot);
1399 }
1400
1401 // Fill in the log entry with the gathered data
1402 logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
1403 logEntryJson["@odata.id"] = boost::urls::format(
1404 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}",
1405 BMCWEB_REDFISH_SYSTEM_URI_NAME, logEntryID);
1406 logEntryJson["Name"] = "System Event Log Entry";
1407 logEntryJson["Id"] = logEntryID;
1408 logEntryJson["Message"] = std::move(msg);
1409 logEntryJson["MessageId"] = std::move(messageID);
1410 logEntryJson["MessageArgs"] = messageArgs;
1411 logEntryJson["EntryType"] = "Event";
1412 logEntryJson["Severity"] = message->messageSeverity;
1413 logEntryJson["Created"] = std::move(timestamp);
1414 return LogParseError::success;
1415 }
1416
fillEventLogLogEntryFromPropertyMap(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const dbus::utility::DBusPropertiesMap & resp,nlohmann::json & objectToFillOut)1417 inline void fillEventLogLogEntryFromPropertyMap(
1418 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1419 const dbus::utility::DBusPropertiesMap& resp,
1420 nlohmann::json& objectToFillOut)
1421 {
1422 std::optional<DbusEventLogEntry> optEntry =
1423 fillDbusEventLogEntryFromPropertyMap(resp);
1424
1425 if (!optEntry.has_value())
1426 {
1427 messages::internalError(asyncResp->res);
1428 return;
1429 }
1430 DbusEventLogEntry entry = optEntry.value();
1431
1432 objectToFillOut["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
1433 objectToFillOut["@odata.id"] = boost::urls::format(
1434 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}",
1435 BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(entry.Id));
1436 objectToFillOut["Name"] = "System Event Log Entry";
1437 objectToFillOut["Id"] = std::to_string(entry.Id);
1438 objectToFillOut["Message"] = entry.Message;
1439 objectToFillOut["Resolved"] = entry.Resolved;
1440 std::optional<bool> notifyAction =
1441 getProviderNotifyAction(entry.ServiceProviderNotify);
1442 if (notifyAction)
1443 {
1444 objectToFillOut["ServiceProviderNotified"] = *notifyAction;
1445 }
1446 if ((entry.Resolution != nullptr) && !entry.Resolution->empty())
1447 {
1448 objectToFillOut["Resolution"] = *entry.Resolution;
1449 }
1450 objectToFillOut["EntryType"] = "Event";
1451 objectToFillOut["Severity"] =
1452 translateSeverityDbusToRedfish(entry.Severity);
1453 objectToFillOut["Created"] =
1454 redfish::time_utils::getDateTimeUintMs(entry.Timestamp);
1455 objectToFillOut["Modified"] =
1456 redfish::time_utils::getDateTimeUintMs(entry.UpdateTimestamp);
1457 if (entry.Path != nullptr)
1458 {
1459 objectToFillOut["AdditionalDataURI"] = boost::urls::format(
1460 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}/attachment",
1461 BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(entry.Id));
1462 }
1463 }
1464
afterLogEntriesGetManagedObjects(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const dbus::utility::ManagedObjectType & resp)1465 inline void afterLogEntriesGetManagedObjects(
1466 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1467 const boost::system::error_code& ec,
1468 const dbus::utility::ManagedObjectType& resp)
1469 {
1470 if (ec)
1471 {
1472 // TODO Handle for specific error code
1473 BMCWEB_LOG_ERROR("getLogEntriesIfaceData resp_handler got error {}",
1474 ec);
1475 messages::internalError(asyncResp->res);
1476 return;
1477 }
1478 nlohmann::json::array_t entriesArray;
1479 for (const auto& objectPath : resp)
1480 {
1481 dbus::utility::DBusPropertiesMap propsFlattened;
1482 auto isEntry =
1483 std::ranges::find_if(objectPath.second, [](const auto& object) {
1484 return object.first == "xyz.openbmc_project.Logging.Entry";
1485 });
1486 if (isEntry == objectPath.second.end())
1487 {
1488 continue;
1489 }
1490 for (const auto& interfaceMap : objectPath.second)
1491 {
1492 for (const auto& propertyMap : interfaceMap.second)
1493 {
1494 propsFlattened.emplace_back(propertyMap.first,
1495 propertyMap.second);
1496 }
1497 }
1498 fillEventLogLogEntryFromPropertyMap(asyncResp, propsFlattened,
1499 entriesArray.emplace_back());
1500 }
1501
1502 std::ranges::sort(entriesArray, [](const nlohmann::json& left,
1503 const nlohmann::json& right) {
1504 return (left["Id"] <= right["Id"]);
1505 });
1506 asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size();
1507 asyncResp->res.jsonValue["Members"] = std::move(entriesArray);
1508 }
1509
handleSystemsLogServiceEventLogLogEntryCollection(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName)1510 inline void handleSystemsLogServiceEventLogLogEntryCollection(
1511 App& app, const crow::Request& req,
1512 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1513 const std::string& systemName)
1514 {
1515 query_param::QueryCapabilities capabilities = {
1516 .canDelegateTop = true,
1517 .canDelegateSkip = true,
1518 };
1519 query_param::Query delegatedQuery;
1520 if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
1521 delegatedQuery, capabilities))
1522 {
1523 return;
1524 }
1525 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1526 {
1527 // Option currently returns no systems. TBD
1528 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1529 systemName);
1530 return;
1531 }
1532 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1533 {
1534 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1535 systemName);
1536 return;
1537 }
1538
1539 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
1540 size_t skip = delegatedQuery.skip.value_or(0);
1541
1542 // Collections don't include the static data added by SubRoute
1543 // because it has a duplicate entry for members
1544 asyncResp->res.jsonValue["@odata.type"] =
1545 "#LogEntryCollection.LogEntryCollection";
1546 asyncResp->res.jsonValue["@odata.id"] =
1547 std::format("/redfish/v1/Systems/{}/LogServices/EventLog/Entries",
1548 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1549 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
1550 asyncResp->res.jsonValue["Description"] =
1551 "Collection of System Event Log Entries";
1552
1553 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
1554 logEntryArray = nlohmann::json::array();
1555 // Go through the log files and create a unique ID for each
1556 // entry
1557 std::vector<std::filesystem::path> redfishLogFiles;
1558 getRedfishLogFiles(redfishLogFiles);
1559 uint64_t entryCount = 0;
1560 std::string logEntry;
1561
1562 // Oldest logs are in the last file, so start there and loop
1563 // backwards
1564 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); it++)
1565 {
1566 std::ifstream logStream(*it);
1567 if (!logStream.is_open())
1568 {
1569 continue;
1570 }
1571
1572 // Reset the unique ID on the first entry
1573 bool firstEntry = true;
1574 while (std::getline(logStream, logEntry))
1575 {
1576 std::string idStr;
1577 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
1578 {
1579 continue;
1580 }
1581 firstEntry = false;
1582
1583 nlohmann::json::object_t bmcLogEntry;
1584 LogParseError status =
1585 fillEventLogEntryJson(idStr, logEntry, bmcLogEntry);
1586 if (status == LogParseError::messageIdNotInRegistry)
1587 {
1588 continue;
1589 }
1590 if (status != LogParseError::success)
1591 {
1592 messages::internalError(asyncResp->res);
1593 return;
1594 }
1595
1596 entryCount++;
1597 // Handle paging using skip (number of entries to skip from the
1598 // start) and top (number of entries to display)
1599 if (entryCount <= skip || entryCount > skip + top)
1600 {
1601 continue;
1602 }
1603
1604 logEntryArray.emplace_back(std::move(bmcLogEntry));
1605 }
1606 }
1607 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
1608 if (skip + top < entryCount)
1609 {
1610 asyncResp->res.jsonValue["Members@odata.nextLink"] =
1611 boost::urls::format(
1612 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries?$skip={}",
1613 BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(skip + top));
1614 }
1615 }
1616
requestRoutesJournalEventLogEntryCollection(App & app)1617 inline void requestRoutesJournalEventLogEntryCollection(App& app)
1618 {
1619 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/")
1620 .privileges(redfish::privileges::getLogEntryCollection)
1621 .methods(boost::beast::http::verb::get)(std::bind_front(
1622 handleSystemsLogServiceEventLogLogEntryCollection, std::ref(app)));
1623 }
1624
handleSystemsLogServiceEventLogEntriesGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName,const std::string & param)1625 inline void handleSystemsLogServiceEventLogEntriesGet(
1626 App& app, const crow::Request& req,
1627 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1628 const std::string& systemName, const std::string& param)
1629 {
1630 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1631 {
1632 return;
1633 }
1634 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1635 {
1636 // Option currently returns no systems. TBD
1637 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1638 systemName);
1639 return;
1640 }
1641
1642 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1643 {
1644 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1645 systemName);
1646 return;
1647 }
1648
1649 const std::string& targetID = param;
1650
1651 // Go through the log files and check the unique ID for each
1652 // entry to find the target entry
1653 std::vector<std::filesystem::path> redfishLogFiles;
1654 getRedfishLogFiles(redfishLogFiles);
1655 std::string logEntry;
1656
1657 // Oldest logs are in the last file, so start there and loop
1658 // backwards
1659 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); it++)
1660 {
1661 std::ifstream logStream(*it);
1662 if (!logStream.is_open())
1663 {
1664 continue;
1665 }
1666
1667 // Reset the unique ID on the first entry
1668 bool firstEntry = true;
1669 while (std::getline(logStream, logEntry))
1670 {
1671 std::string idStr;
1672 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
1673 {
1674 continue;
1675 }
1676 firstEntry = false;
1677
1678 if (idStr == targetID)
1679 {
1680 nlohmann::json::object_t bmcLogEntry;
1681 LogParseError status =
1682 fillEventLogEntryJson(idStr, logEntry, bmcLogEntry);
1683 if (status != LogParseError::success)
1684 {
1685 messages::internalError(asyncResp->res);
1686 return;
1687 }
1688 asyncResp->res.jsonValue.update(bmcLogEntry);
1689 return;
1690 }
1691 }
1692 }
1693 // Requested ID was not found
1694 messages::resourceNotFound(asyncResp->res, "LogEntry", targetID);
1695 }
1696
requestRoutesJournalEventLogEntry(App & app)1697 inline void requestRoutesJournalEventLogEntry(App& app)
1698 {
1699 BMCWEB_ROUTE(
1700 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
1701 .privileges(redfish::privileges::getLogEntry)
1702 .methods(boost::beast::http::verb::get)(std::bind_front(
1703 handleSystemsLogServiceEventLogEntriesGet, std::ref(app)));
1704 }
1705
dBusEventLogEntryCollection(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1706 inline void dBusEventLogEntryCollection(
1707 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1708 {
1709 // Collections don't include the static data added by SubRoute
1710 // because it has a duplicate entry for members
1711 asyncResp->res.jsonValue["@odata.type"] =
1712 "#LogEntryCollection.LogEntryCollection";
1713 asyncResp->res.jsonValue["@odata.id"] =
1714 std::format("/redfish/v1/Systems/{}/LogServices/EventLog/Entries",
1715 BMCWEB_REDFISH_SYSTEM_URI_NAME);
1716 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
1717 asyncResp->res.jsonValue["Description"] =
1718 "Collection of System Event Log Entries";
1719
1720 // DBus implementation of EventLog/Entries
1721 // Make call to Logging Service to find all log entry objects
1722 sdbusplus::message::object_path path("/xyz/openbmc_project/logging");
1723 dbus::utility::getManagedObjects(
1724 "xyz.openbmc_project.Logging", path,
1725 [asyncResp](const boost::system::error_code& ec,
1726 const dbus::utility::ManagedObjectType& resp) {
1727 afterLogEntriesGetManagedObjects(asyncResp, ec, resp);
1728 });
1729 }
1730
requestRoutesDBusEventLogEntryCollection(App & app)1731 inline void requestRoutesDBusEventLogEntryCollection(App& app)
1732 {
1733 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/")
1734 .privileges(redfish::privileges::getLogEntryCollection)
1735 .methods(boost::beast::http::verb::get)(
1736 [&app](const crow::Request& req,
1737 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1738 const std::string& systemName) {
1739 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1740 {
1741 return;
1742 }
1743 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1744 {
1745 // Option currently returns no systems. TBD
1746 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1747 systemName);
1748 return;
1749 }
1750 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1751 {
1752 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1753 systemName);
1754 return;
1755 }
1756 dBusEventLogEntryCollection(asyncResp);
1757 });
1758 }
1759
dBusEventLogEntryGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string entryID)1760 inline void dBusEventLogEntryGet(
1761 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string entryID)
1762 {
1763 dbus::utility::escapePathForDbus(entryID);
1764
1765 // DBus implementation of EventLog/Entries
1766 // Make call to Logging Service to find all log entry objects
1767 sdbusplus::asio::getAllProperties(
1768 *crow::connections::systemBus, "xyz.openbmc_project.Logging",
1769 "/xyz/openbmc_project/logging/entry/" + entryID, "",
1770 [asyncResp, entryID](const boost::system::error_code& ec,
1771 const dbus::utility::DBusPropertiesMap& resp) {
1772 if (ec.value() == EBADR)
1773 {
1774 messages::resourceNotFound(asyncResp->res, "EventLogEntry",
1775 entryID);
1776 return;
1777 }
1778 if (ec)
1779 {
1780 BMCWEB_LOG_ERROR(
1781 "EventLogEntry (DBus) resp_handler got error {}", ec);
1782 messages::internalError(asyncResp->res);
1783 return;
1784 }
1785
1786 fillEventLogLogEntryFromPropertyMap(asyncResp, resp,
1787 asyncResp->res.jsonValue);
1788 });
1789 }
1790
1791 inline void
dBusEventLogEntryPatch(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & entryId)1792 dBusEventLogEntryPatch(const crow::Request& req,
1793 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1794 const std::string& entryId)
1795 {
1796 std::optional<bool> resolved;
1797
1798 if (!json_util::readJsonPatch(req, asyncResp->res, "Resolved", resolved))
1799 {
1800 return;
1801 }
1802 BMCWEB_LOG_DEBUG("Set Resolved");
1803
1804 setDbusProperty(asyncResp, "Resolved", "xyz.openbmc_project.Logging",
1805 "/xyz/openbmc_project/logging/entry/" + entryId,
1806 "xyz.openbmc_project.Logging.Entry", "Resolved",
1807 resolved.value_or(false));
1808 }
1809
dBusEventLogEntryDelete(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string entryID)1810 inline void dBusEventLogEntryDelete(
1811 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string entryID)
1812 {
1813 BMCWEB_LOG_DEBUG("Do delete single event entries.");
1814
1815 dbus::utility::escapePathForDbus(entryID);
1816
1817 // Process response from Logging service.
1818 auto respHandler = [asyncResp,
1819 entryID](const boost::system::error_code& ec) {
1820 BMCWEB_LOG_DEBUG("EventLogEntry (DBus) doDelete callback: Done");
1821 if (ec)
1822 {
1823 if (ec.value() == EBADR)
1824 {
1825 messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
1826 return;
1827 }
1828 // TODO Handle for specific error code
1829 BMCWEB_LOG_ERROR(
1830 "EventLogEntry (DBus) doDelete respHandler got error {}", ec);
1831 asyncResp->res.result(
1832 boost::beast::http::status::internal_server_error);
1833 return;
1834 }
1835
1836 asyncResp->res.result(boost::beast::http::status::ok);
1837 };
1838
1839 // Make call to Logging service to request Delete Log
1840 crow::connections::systemBus->async_method_call(
1841 respHandler, "xyz.openbmc_project.Logging",
1842 "/xyz/openbmc_project/logging/entry/" + entryID,
1843 "xyz.openbmc_project.Object.Delete", "Delete");
1844 }
1845
requestRoutesDBusEventLogEntry(App & app)1846 inline void requestRoutesDBusEventLogEntry(App& app)
1847 {
1848 BMCWEB_ROUTE(
1849 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
1850 .privileges(redfish::privileges::getLogEntry)
1851 .methods(boost::beast::http::verb::get)(
1852 [&app](const crow::Request& req,
1853 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1854 const std::string& systemName, const std::string& entryId) {
1855 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1856 {
1857 return;
1858 }
1859 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1860 {
1861 // Option currently returns no systems. TBD
1862 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1863 systemName);
1864 return;
1865 }
1866 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1867 {
1868 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1869 systemName);
1870 return;
1871 }
1872
1873 dBusEventLogEntryGet(asyncResp, entryId);
1874 });
1875
1876 BMCWEB_ROUTE(
1877 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
1878 .privileges(redfish::privileges::patchLogEntry)
1879 .methods(boost::beast::http::verb::patch)(
1880 [&app](const crow::Request& req,
1881 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1882 const std::string& systemName, const std::string& entryId) {
1883 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1884 {
1885 return;
1886 }
1887 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1888 {
1889 // Option currently returns no systems. TBD
1890 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1891 systemName);
1892 return;
1893 }
1894 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1895 {
1896 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1897 systemName);
1898 return;
1899 }
1900
1901 dBusEventLogEntryPatch(req, asyncResp, entryId);
1902 });
1903
1904 BMCWEB_ROUTE(
1905 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
1906 .privileges(redfish::privileges::deleteLogEntry)
1907
1908 .methods(boost::beast::http::verb::delete_)(
1909 [&app](const crow::Request& req,
1910 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1911 const std::string& systemName, const std::string& param) {
1912 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1913 {
1914 return;
1915 }
1916 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
1917 {
1918 // Option currently returns no systems. TBD
1919 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1920 systemName);
1921 return;
1922 }
1923 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
1924 {
1925 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1926 systemName);
1927 return;
1928 }
1929 dBusEventLogEntryDelete(asyncResp, param);
1930 });
1931 }
1932
1933 constexpr const char* hostLoggerFolderPath = "/var/log/console";
1934
1935 inline bool
getHostLoggerFiles(const std::string & hostLoggerFilePath,std::vector<std::filesystem::path> & hostLoggerFiles)1936 getHostLoggerFiles(const std::string& hostLoggerFilePath,
1937 std::vector<std::filesystem::path>& hostLoggerFiles)
1938 {
1939 std::error_code ec;
1940 std::filesystem::directory_iterator logPath(hostLoggerFilePath, ec);
1941 if (ec)
1942 {
1943 BMCWEB_LOG_WARNING("{}", ec.message());
1944 return false;
1945 }
1946 for (const std::filesystem::directory_entry& it : logPath)
1947 {
1948 std::string filename = it.path().filename();
1949 // Prefix of each log files is "log". Find the file and save the
1950 // path
1951 if (filename.starts_with("log"))
1952 {
1953 hostLoggerFiles.emplace_back(it.path());
1954 }
1955 }
1956 // As the log files rotate, they are appended with a ".#" that is higher for
1957 // the older logs. Since we start from oldest logs, sort the name in
1958 // descending order.
1959 std::sort(hostLoggerFiles.rbegin(), hostLoggerFiles.rend(),
1960 AlphanumLess<std::string>());
1961
1962 return true;
1963 }
1964
getHostLoggerEntries(const std::vector<std::filesystem::path> & hostLoggerFiles,uint64_t skip,uint64_t top,std::vector<std::string> & logEntries,size_t & logCount)1965 inline bool getHostLoggerEntries(
1966 const std::vector<std::filesystem::path>& hostLoggerFiles, uint64_t skip,
1967 uint64_t top, std::vector<std::string>& logEntries, size_t& logCount)
1968 {
1969 GzFileReader logFile;
1970
1971 // Go though all log files and expose host logs.
1972 for (const std::filesystem::path& it : hostLoggerFiles)
1973 {
1974 if (!logFile.gzGetLines(it.string(), skip, top, logEntries, logCount))
1975 {
1976 BMCWEB_LOG_ERROR("fail to expose host logs");
1977 return false;
1978 }
1979 }
1980 // Get lastMessage from constructor by getter
1981 std::string lastMessage = logFile.getLastMessage();
1982 if (!lastMessage.empty())
1983 {
1984 logCount++;
1985 if (logCount > skip && logCount <= (skip + top))
1986 {
1987 logEntries.push_back(lastMessage);
1988 }
1989 }
1990 return true;
1991 }
1992
handleBMCLogServicesCollectionGet(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)1993 inline void handleBMCLogServicesCollectionGet(
1994 crow::App& app, const crow::Request& req,
1995 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1996 const std::string& managerId)
1997 {
1998 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1999 {
2000 return;
2001 }
2002
2003 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2004 {
2005 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2006 return;
2007 }
2008
2009 // Collections don't include the static data added by SubRoute
2010 // because it has a duplicate entry for members
2011 asyncResp->res.jsonValue["@odata.type"] =
2012 "#LogServiceCollection.LogServiceCollection";
2013 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
2014 "/redfish/v1/Managers/{}/LogServices", BMCWEB_REDFISH_MANAGER_URI_NAME);
2015 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
2016 asyncResp->res.jsonValue["Description"] =
2017 "Collection of LogServices for this Manager";
2018 nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"];
2019 logServiceArray = nlohmann::json::array();
2020
2021 if constexpr (BMCWEB_REDFISH_BMC_JOURNAL)
2022 {
2023 nlohmann::json::object_t journal;
2024 journal["@odata.id"] =
2025 boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal",
2026 BMCWEB_REDFISH_MANAGER_URI_NAME);
2027 logServiceArray.emplace_back(std::move(journal));
2028 }
2029
2030 asyncResp->res.jsonValue["Members@odata.count"] = logServiceArray.size();
2031
2032 if constexpr (BMCWEB_REDFISH_DUMP_LOG)
2033 {
2034 constexpr std::array<std::string_view, 1> interfaces = {
2035 "xyz.openbmc_project.Collection.DeleteAll"};
2036 dbus::utility::getSubTreePaths(
2037 "/xyz/openbmc_project/dump", 0, interfaces,
2038 [asyncResp](const boost::system::error_code& ec,
2039 const dbus::utility::MapperGetSubTreePathsResponse&
2040 subTreePaths) {
2041 if (ec)
2042 {
2043 BMCWEB_LOG_ERROR(
2044 "handleBMCLogServicesCollectionGet respHandler got error {}",
2045 ec);
2046 // Assume that getting an error simply means there are no
2047 // dump LogServices. Return without adding any error
2048 // response.
2049 return;
2050 }
2051
2052 nlohmann::json& logServiceArrayLocal =
2053 asyncResp->res.jsonValue["Members"];
2054
2055 for (const std::string& path : subTreePaths)
2056 {
2057 if (path == "/xyz/openbmc_project/dump/bmc")
2058 {
2059 nlohmann::json::object_t member;
2060 member["@odata.id"] = boost::urls::format(
2061 "/redfish/v1/Managers/{}/LogServices/Dump",
2062 BMCWEB_REDFISH_MANAGER_URI_NAME);
2063 logServiceArrayLocal.emplace_back(std::move(member));
2064 }
2065 else if (path == "/xyz/openbmc_project/dump/faultlog")
2066 {
2067 nlohmann::json::object_t member;
2068 member["@odata.id"] = boost::urls::format(
2069 "/redfish/v1/Managers/{}/LogServices/FaultLog",
2070 BMCWEB_REDFISH_MANAGER_URI_NAME);
2071 logServiceArrayLocal.emplace_back(std::move(member));
2072 }
2073 }
2074
2075 asyncResp->res.jsonValue["Members@odata.count"] =
2076 logServiceArrayLocal.size();
2077 });
2078 }
2079 }
2080
requestRoutesBMCLogServiceCollection(App & app)2081 inline void requestRoutesBMCLogServiceCollection(App& app)
2082 {
2083 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/")
2084 .privileges(redfish::privileges::getLogServiceCollection)
2085 .methods(boost::beast::http::verb::get)(
2086 std::bind_front(handleBMCLogServicesCollectionGet, std::ref(app)));
2087 }
2088
2089 inline void
getDumpServiceInfo(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & dumpType)2090 getDumpServiceInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2091 const std::string& dumpType)
2092 {
2093 std::string dumpPath;
2094 log_service::OverWritePolicy overWritePolicy =
2095 log_service::OverWritePolicy::Invalid;
2096 bool collectDiagnosticDataSupported = false;
2097
2098 if (dumpType == "BMC")
2099 {
2100 dumpPath = std::format("/redfish/v1/Managers/{}/LogServices/Dump",
2101 BMCWEB_REDFISH_MANAGER_URI_NAME);
2102 overWritePolicy = log_service::OverWritePolicy::WrapsWhenFull;
2103 collectDiagnosticDataSupported = true;
2104 }
2105 else if (dumpType == "FaultLog")
2106 {
2107 dumpPath = std::format("/redfish/v1/Managers/{}/LogServices/FaultLog",
2108 BMCWEB_REDFISH_MANAGER_URI_NAME);
2109 overWritePolicy = log_service::OverWritePolicy::Unknown;
2110 collectDiagnosticDataSupported = false;
2111 }
2112 else if (dumpType == "System")
2113 {
2114 dumpPath = std::format("/redfish/v1/Systems/{}/LogServices/Dump",
2115 BMCWEB_REDFISH_SYSTEM_URI_NAME);
2116 overWritePolicy = log_service::OverWritePolicy::WrapsWhenFull;
2117 collectDiagnosticDataSupported = true;
2118 }
2119 else
2120 {
2121 BMCWEB_LOG_ERROR("getDumpServiceInfo() invalid dump type: {}",
2122 dumpType);
2123 messages::internalError(asyncResp->res);
2124 return;
2125 }
2126
2127 asyncResp->res.jsonValue["@odata.id"] = dumpPath;
2128 asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
2129 asyncResp->res.jsonValue["Name"] = "Dump LogService";
2130 asyncResp->res.jsonValue["Description"] = dumpType + " Dump LogService";
2131 asyncResp->res.jsonValue["Id"] = std::filesystem::path(dumpPath).filename();
2132 asyncResp->res.jsonValue["OverWritePolicy"] = overWritePolicy;
2133
2134 std::pair<std::string, std::string> redfishDateTimeOffset =
2135 redfish::time_utils::getDateTimeOffsetNow();
2136 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
2137 asyncResp->res.jsonValue["DateTimeLocalOffset"] =
2138 redfishDateTimeOffset.second;
2139
2140 asyncResp->res.jsonValue["Entries"]["@odata.id"] = dumpPath + "/Entries";
2141
2142 if (collectDiagnosticDataSupported)
2143 {
2144 asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"]
2145 ["target"] =
2146 dumpPath + "/Actions/LogService.CollectDiagnosticData";
2147 }
2148
2149 constexpr std::array<std::string_view, 1> interfaces = {deleteAllInterface};
2150 dbus::utility::getSubTreePaths(
2151 "/xyz/openbmc_project/dump", 0, interfaces,
2152 [asyncResp, dumpType, dumpPath](
2153 const boost::system::error_code& ec,
2154 const dbus::utility::MapperGetSubTreePathsResponse& subTreePaths) {
2155 if (ec)
2156 {
2157 BMCWEB_LOG_ERROR("getDumpServiceInfo respHandler got error {}",
2158 ec);
2159 // Assume that getting an error simply means there are no dump
2160 // LogServices. Return without adding any error response.
2161 return;
2162 }
2163 std::string dbusDumpPath = getDumpPath(dumpType);
2164 for (const std::string& path : subTreePaths)
2165 {
2166 if (path == dbusDumpPath)
2167 {
2168 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]
2169 ["target"] =
2170 dumpPath + "/Actions/LogService.ClearLog";
2171 break;
2172 }
2173 }
2174 });
2175 }
2176
handleLogServicesDumpServiceGet(crow::App & app,const std::string & dumpType,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)2177 inline void handleLogServicesDumpServiceGet(
2178 crow::App& app, const std::string& dumpType, const crow::Request& req,
2179 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2180 const std::string& managerId)
2181 {
2182 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2183 {
2184 return;
2185 }
2186
2187 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2188 {
2189 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2190 return;
2191 }
2192
2193 getDumpServiceInfo(asyncResp, dumpType);
2194 }
2195
handleLogServicesDumpServiceComputerSystemGet(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)2196 inline void handleLogServicesDumpServiceComputerSystemGet(
2197 crow::App& app, const crow::Request& req,
2198 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2199 const std::string& chassisId)
2200 {
2201 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2202 {
2203 return;
2204 }
2205 if (chassisId != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2206 {
2207 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
2208 return;
2209 }
2210 getDumpServiceInfo(asyncResp, "System");
2211 }
2212
handleLogServicesDumpEntriesCollectionGet(crow::App & app,const std::string & dumpType,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)2213 inline void handleLogServicesDumpEntriesCollectionGet(
2214 crow::App& app, const std::string& dumpType, const crow::Request& req,
2215 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2216 const std::string& managerId)
2217 {
2218 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2219 {
2220 return;
2221 }
2222
2223 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2224 {
2225 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2226 return;
2227 }
2228 getDumpEntryCollection(asyncResp, dumpType);
2229 }
2230
handleLogServicesDumpEntriesCollectionComputerSystemGet(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)2231 inline void handleLogServicesDumpEntriesCollectionComputerSystemGet(
2232 crow::App& app, const crow::Request& req,
2233 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2234 const std::string& chassisId)
2235 {
2236 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2237 {
2238 return;
2239 }
2240 if (chassisId != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2241 {
2242 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
2243 return;
2244 }
2245 getDumpEntryCollection(asyncResp, "System");
2246 }
2247
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)2248 inline void handleLogServicesDumpEntryGet(
2249 crow::App& app, const std::string& dumpType, const crow::Request& req,
2250 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2251 const std::string& managerId, const std::string& dumpId)
2252 {
2253 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2254 {
2255 return;
2256 }
2257 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2258 {
2259 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2260 return;
2261 }
2262 getDumpEntryById(asyncResp, dumpId, dumpType);
2263 }
2264
handleLogServicesDumpEntryComputerSystemGet(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & dumpId)2265 inline void handleLogServicesDumpEntryComputerSystemGet(
2266 crow::App& app, const crow::Request& req,
2267 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2268 const std::string& chassisId, const std::string& dumpId)
2269 {
2270 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2271 {
2272 return;
2273 }
2274 if (chassisId != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2275 {
2276 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
2277 return;
2278 }
2279 getDumpEntryById(asyncResp, dumpId, "System");
2280 }
2281
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)2282 inline void handleLogServicesDumpEntryDelete(
2283 crow::App& app, const std::string& dumpType, const crow::Request& req,
2284 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2285 const std::string& managerId, const std::string& dumpId)
2286 {
2287 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2288 {
2289 return;
2290 }
2291
2292 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2293 {
2294 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2295 return;
2296 }
2297 deleteDumpEntry(asyncResp, dumpId, dumpType);
2298 }
2299
handleLogServicesDumpEntryComputerSystemDelete(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & dumpId)2300 inline void handleLogServicesDumpEntryComputerSystemDelete(
2301 crow::App& app, const crow::Request& req,
2302 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2303 const std::string& chassisId, const std::string& dumpId)
2304 {
2305 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2306 {
2307 return;
2308 }
2309 if (chassisId != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2310 {
2311 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
2312 return;
2313 }
2314 deleteDumpEntry(asyncResp, dumpId, "System");
2315 }
2316
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)2317 inline void handleLogServicesDumpEntryDownloadGet(
2318 crow::App& app, const std::string& dumpType, const crow::Request& req,
2319 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2320 const std::string& managerId, const std::string& dumpId)
2321 {
2322 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2323 {
2324 return;
2325 }
2326
2327 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2328 {
2329 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2330 return;
2331 }
2332 downloadDumpEntry(asyncResp, dumpId, dumpType);
2333 }
2334
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)2335 inline void handleDBusEventLogEntryDownloadGet(
2336 crow::App& app, const std::string& dumpType, const crow::Request& req,
2337 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2338 const std::string& systemName, const std::string& entryID)
2339 {
2340 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2341 {
2342 return;
2343 }
2344 if (!http_helpers::isContentTypeAllowed(
2345 req.getHeaderValue("Accept"),
2346 http_helpers::ContentType::OctetStream, true))
2347 {
2348 asyncResp->res.result(boost::beast::http::status::bad_request);
2349 return;
2350 }
2351 downloadEventLogEntry(asyncResp, systemName, entryID, dumpType);
2352 }
2353
handleLogServicesDumpCollectDiagnosticDataPost(crow::App & app,const std::string & dumpType,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)2354 inline void handleLogServicesDumpCollectDiagnosticDataPost(
2355 crow::App& app, const std::string& dumpType, const crow::Request& req,
2356 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2357 const std::string& managerId)
2358 {
2359 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2360 {
2361 return;
2362 }
2363 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2364 {
2365 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2366 return;
2367 }
2368
2369 createDump(asyncResp, req, dumpType);
2370 }
2371
handleLogServicesDumpCollectDiagnosticDataComputerSystemPost(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName)2372 inline void handleLogServicesDumpCollectDiagnosticDataComputerSystemPost(
2373 crow::App& app, const crow::Request& req,
2374 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2375 const std::string& systemName)
2376 {
2377 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2378 {
2379 return;
2380 }
2381
2382 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2383 {
2384 // Option currently returns no systems. TBD
2385 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2386 systemName);
2387 return;
2388 }
2389 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2390 {
2391 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2392 systemName);
2393 return;
2394 }
2395 createDump(asyncResp, req, "System");
2396 }
2397
handleLogServicesDumpClearLogPost(crow::App & app,const std::string & dumpType,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)2398 inline void handleLogServicesDumpClearLogPost(
2399 crow::App& app, const std::string& dumpType, const crow::Request& req,
2400 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2401 const std::string& managerId)
2402 {
2403 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2404 {
2405 return;
2406 }
2407
2408 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2409 {
2410 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
2411 return;
2412 }
2413 clearDump(asyncResp, dumpType);
2414 }
2415
handleLogServicesDumpClearLogComputerSystemPost(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName)2416 inline void handleLogServicesDumpClearLogComputerSystemPost(
2417 crow::App& app, const crow::Request& req,
2418 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2419 const std::string& systemName)
2420 {
2421 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2422 {
2423 return;
2424 }
2425 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2426 {
2427 // Option currently returns no systems. TBD
2428 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2429 systemName);
2430 return;
2431 }
2432 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2433 {
2434 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2435 systemName);
2436 return;
2437 }
2438 clearDump(asyncResp, "System");
2439 }
2440
requestRoutesBMCDumpService(App & app)2441 inline void requestRoutesBMCDumpService(App& app)
2442 {
2443 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Dump/")
2444 .privileges(redfish::privileges::getLogService)
2445 .methods(boost::beast::http::verb::get)(std::bind_front(
2446 handleLogServicesDumpServiceGet, std::ref(app), "BMC"));
2447 }
2448
requestRoutesBMCDumpEntryCollection(App & app)2449 inline void requestRoutesBMCDumpEntryCollection(App& app)
2450 {
2451 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Dump/Entries/")
2452 .privileges(redfish::privileges::getLogEntryCollection)
2453 .methods(boost::beast::http::verb::get)(std::bind_front(
2454 handleLogServicesDumpEntriesCollectionGet, std::ref(app), "BMC"));
2455 }
2456
requestRoutesBMCDumpEntry(App & app)2457 inline void requestRoutesBMCDumpEntry(App& app)
2458 {
2459 BMCWEB_ROUTE(app,
2460 "/redfish/v1/Managers/<str>/LogServices/Dump/Entries/<str>/")
2461 .privileges(redfish::privileges::getLogEntry)
2462 .methods(boost::beast::http::verb::get)(std::bind_front(
2463 handleLogServicesDumpEntryGet, std::ref(app), "BMC"));
2464
2465 BMCWEB_ROUTE(app,
2466 "/redfish/v1/Managers/<str>/LogServices/Dump/Entries/<str>/")
2467 .privileges(redfish::privileges::deleteLogEntry)
2468 .methods(boost::beast::http::verb::delete_)(std::bind_front(
2469 handleLogServicesDumpEntryDelete, std::ref(app), "BMC"));
2470 }
2471
requestRoutesBMCDumpEntryDownload(App & app)2472 inline void requestRoutesBMCDumpEntryDownload(App& app)
2473 {
2474 BMCWEB_ROUTE(
2475 app,
2476 "/redfish/v1/Managers/<str>/LogServices/Dump/Entries/<str>/attachment/")
2477 .privileges(redfish::privileges::getLogEntry)
2478 .methods(boost::beast::http::verb::get)(std::bind_front(
2479 handleLogServicesDumpEntryDownloadGet, std::ref(app), "BMC"));
2480 }
2481
requestRoutesBMCDumpCreate(App & app)2482 inline void requestRoutesBMCDumpCreate(App& app)
2483 {
2484 BMCWEB_ROUTE(
2485 app,
2486 "/redfish/v1/Managers/<str>/LogServices/Dump/Actions/LogService.CollectDiagnosticData/")
2487 .privileges(redfish::privileges::postLogService)
2488 .methods(boost::beast::http::verb::post)(
2489 std::bind_front(handleLogServicesDumpCollectDiagnosticDataPost,
2490 std::ref(app), "BMC"));
2491 }
2492
requestRoutesBMCDumpClear(App & app)2493 inline void requestRoutesBMCDumpClear(App& app)
2494 {
2495 BMCWEB_ROUTE(
2496 app,
2497 "/redfish/v1/Managers/<str>/LogServices/Dump/Actions/LogService.ClearLog/")
2498 .privileges(redfish::privileges::postLogService)
2499 .methods(boost::beast::http::verb::post)(std::bind_front(
2500 handleLogServicesDumpClearLogPost, std::ref(app), "BMC"));
2501 }
2502
requestRoutesDBusEventLogEntryDownload(App & app)2503 inline void requestRoutesDBusEventLogEntryDownload(App& app)
2504 {
2505 BMCWEB_ROUTE(
2506 app,
2507 "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/attachment/")
2508 .privileges(redfish::privileges::getLogEntry)
2509 .methods(boost::beast::http::verb::get)(std::bind_front(
2510 handleDBusEventLogEntryDownloadGet, std::ref(app), "System"));
2511 }
2512
requestRoutesFaultLogDumpService(App & app)2513 inline void requestRoutesFaultLogDumpService(App& app)
2514 {
2515 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/FaultLog/")
2516 .privileges(redfish::privileges::getLogService)
2517 .methods(boost::beast::http::verb::get)(std::bind_front(
2518 handleLogServicesDumpServiceGet, std::ref(app), "FaultLog"));
2519 }
2520
requestRoutesFaultLogDumpEntryCollection(App & app)2521 inline void requestRoutesFaultLogDumpEntryCollection(App& app)
2522 {
2523 BMCWEB_ROUTE(app,
2524 "/redfish/v1/Managers/<str>/LogServices/FaultLog/Entries/")
2525 .privileges(redfish::privileges::getLogEntryCollection)
2526 .methods(boost::beast::http::verb::get)(
2527 std::bind_front(handleLogServicesDumpEntriesCollectionGet,
2528 std::ref(app), "FaultLog"));
2529 }
2530
requestRoutesFaultLogDumpEntry(App & app)2531 inline void requestRoutesFaultLogDumpEntry(App& app)
2532 {
2533 BMCWEB_ROUTE(
2534 app, "/redfish/v1/Managers/<str>/LogServices/FaultLog/Entries/<str>/")
2535 .privileges(redfish::privileges::getLogEntry)
2536 .methods(boost::beast::http::verb::get)(std::bind_front(
2537 handleLogServicesDumpEntryGet, std::ref(app), "FaultLog"));
2538
2539 BMCWEB_ROUTE(
2540 app, "/redfish/v1/Managers/<str>/LogServices/FaultLog/Entries/<str>/")
2541 .privileges(redfish::privileges::deleteLogEntry)
2542 .methods(boost::beast::http::verb::delete_)(std::bind_front(
2543 handleLogServicesDumpEntryDelete, std::ref(app), "FaultLog"));
2544 }
2545
requestRoutesFaultLogDumpClear(App & app)2546 inline void requestRoutesFaultLogDumpClear(App& app)
2547 {
2548 BMCWEB_ROUTE(
2549 app,
2550 "/redfish/v1/Managers/<str>/LogServices/FaultLog/Actions/LogService.ClearLog/")
2551 .privileges(redfish::privileges::postLogService)
2552 .methods(boost::beast::http::verb::post)(std::bind_front(
2553 handleLogServicesDumpClearLogPost, std::ref(app), "FaultLog"));
2554 }
2555
requestRoutesSystemDumpService(App & app)2556 inline void requestRoutesSystemDumpService(App& app)
2557 {
2558 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Dump/")
2559 .privileges(redfish::privileges::getLogService)
2560 .methods(boost::beast::http::verb::get)(std::bind_front(
2561 handleLogServicesDumpServiceComputerSystemGet, std::ref(app)));
2562 }
2563
requestRoutesSystemDumpEntryCollection(App & app)2564 inline void requestRoutesSystemDumpEntryCollection(App& app)
2565 {
2566 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/")
2567 .privileges(redfish::privileges::getLogEntryCollection)
2568 .methods(boost::beast::http::verb::get)(std::bind_front(
2569 handleLogServicesDumpEntriesCollectionComputerSystemGet,
2570 std::ref(app)));
2571 }
2572
requestRoutesSystemDumpEntry(App & app)2573 inline void requestRoutesSystemDumpEntry(App& app)
2574 {
2575 BMCWEB_ROUTE(app,
2576 "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/<str>/")
2577 .privileges(redfish::privileges::getLogEntry)
2578 .methods(boost::beast::http::verb::get)(std::bind_front(
2579 handleLogServicesDumpEntryComputerSystemGet, std::ref(app)));
2580
2581 BMCWEB_ROUTE(app,
2582 "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/<str>/")
2583 .privileges(redfish::privileges::deleteLogEntry)
2584 .methods(boost::beast::http::verb::delete_)(std::bind_front(
2585 handleLogServicesDumpEntryComputerSystemDelete, std::ref(app)));
2586 }
2587
requestRoutesSystemDumpCreate(App & app)2588 inline void requestRoutesSystemDumpCreate(App& app)
2589 {
2590 BMCWEB_ROUTE(
2591 app,
2592 "/redfish/v1/Systems/<str>/LogServices/Dump/Actions/LogService.CollectDiagnosticData/")
2593 .privileges(redfish::privileges::postLogService)
2594 .methods(boost::beast::http::verb::post)(std::bind_front(
2595 handleLogServicesDumpCollectDiagnosticDataComputerSystemPost,
2596 std::ref(app)));
2597 }
2598
requestRoutesSystemDumpClear(App & app)2599 inline void requestRoutesSystemDumpClear(App& app)
2600 {
2601 BMCWEB_ROUTE(
2602 app,
2603 "/redfish/v1/Systems/<str>/LogServices/Dump/Actions/LogService.ClearLog/")
2604 .privileges(redfish::privileges::postLogService)
2605 .methods(boost::beast::http::verb::post)(std::bind_front(
2606 handleLogServicesDumpClearLogComputerSystemPost, std::ref(app)));
2607 }
2608
requestRoutesCrashdumpService(App & app)2609 inline void requestRoutesCrashdumpService(App& app)
2610 {
2611 // Note: Deviated from redfish privilege registry for GET & HEAD
2612 // method for security reasons.
2613 /**
2614 * Functions triggers appropriate requests on DBus
2615 */
2616 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Crashdump/")
2617 // This is incorrect, should be:
2618 //.privileges(redfish::privileges::getLogService)
2619 .privileges({{"ConfigureManager"}})
2620 .methods(
2621 boost::beast::http::verb::
2622 get)([&app](const crow::Request& req,
2623 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2624 const std::string& systemName) {
2625 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2626 {
2627 return;
2628 }
2629 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2630 {
2631 // Option currently returns no systems. TBD
2632 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2633 systemName);
2634 return;
2635 }
2636 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2637 {
2638 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2639 systemName);
2640 return;
2641 }
2642
2643 // Copy over the static data to include the entries added by
2644 // SubRoute
2645 asyncResp->res.jsonValue["@odata.id"] =
2646 std::format("/redfish/v1/Systems/{}/LogServices/Crashdump",
2647 BMCWEB_REDFISH_SYSTEM_URI_NAME);
2648 asyncResp->res.jsonValue["@odata.type"] =
2649 "#LogService.v1_2_0.LogService";
2650 asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service";
2651 asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service";
2652 asyncResp->res.jsonValue["Id"] = "Crashdump";
2653 asyncResp->res.jsonValue["OverWritePolicy"] =
2654 log_service::OverWritePolicy::WrapsWhenFull;
2655 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
2656
2657 std::pair<std::string, std::string> redfishDateTimeOffset =
2658 redfish::time_utils::getDateTimeOffsetNow();
2659 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
2660 asyncResp->res.jsonValue["DateTimeLocalOffset"] =
2661 redfishDateTimeOffset.second;
2662
2663 asyncResp->res.jsonValue["Entries"]["@odata.id"] = std::format(
2664 "/redfish/v1/Systems/{}/LogServices/Crashdump/Entries",
2665 BMCWEB_REDFISH_SYSTEM_URI_NAME);
2666 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]
2667 ["target"] = std::format(
2668 "/redfish/v1/Systems/{}/LogServices/Crashdump/Actions/LogService.ClearLog",
2669 BMCWEB_REDFISH_SYSTEM_URI_NAME);
2670 asyncResp->res
2671 .jsonValue["Actions"]["#LogService.CollectDiagnosticData"]
2672 ["target"] = std::format(
2673 "/redfish/v1/Systems/{}/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData",
2674 BMCWEB_REDFISH_SYSTEM_URI_NAME);
2675 });
2676 }
2677
requestRoutesCrashdumpClear(App & app)2678 void inline requestRoutesCrashdumpClear(App& app)
2679 {
2680 BMCWEB_ROUTE(
2681 app,
2682 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Actions/LogService.ClearLog/")
2683 // This is incorrect, should be:
2684 //.privileges(redfish::privileges::postLogService)
2685 .privileges({{"ConfigureComponents"}})
2686 .methods(boost::beast::http::verb::post)(
2687 [&app](const crow::Request& req,
2688 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2689 const std::string& systemName) {
2690 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2691 {
2692 return;
2693 }
2694 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2695 {
2696 // Option currently returns no systems. TBD
2697 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2698 systemName);
2699 return;
2700 }
2701 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2702 {
2703 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2704 systemName);
2705 return;
2706 }
2707 crow::connections::systemBus->async_method_call(
2708 [asyncResp](const boost::system::error_code& ec,
2709 const std::string&) {
2710 if (ec)
2711 {
2712 messages::internalError(asyncResp->res);
2713 return;
2714 }
2715 messages::success(asyncResp->res);
2716 },
2717 crashdumpObject, crashdumpPath, deleteAllInterface,
2718 "DeleteAll");
2719 });
2720 }
2721
2722 inline void
logCrashdumpEntry(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & logID,nlohmann::json & logEntryJson)2723 logCrashdumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2724 const std::string& logID, nlohmann::json& logEntryJson)
2725 {
2726 auto getStoredLogCallback =
2727 [asyncResp, logID,
2728 &logEntryJson](const boost::system::error_code& ec,
2729 const dbus::utility::DBusPropertiesMap& params) {
2730 if (ec)
2731 {
2732 BMCWEB_LOG_DEBUG("failed to get log ec: {}", ec.message());
2733 if (ec.value() ==
2734 boost::system::linux_error::bad_request_descriptor)
2735 {
2736 messages::resourceNotFound(asyncResp->res, "LogEntry",
2737 logID);
2738 }
2739 else
2740 {
2741 messages::internalError(asyncResp->res);
2742 }
2743 return;
2744 }
2745
2746 std::string timestamp{};
2747 std::string filename{};
2748 std::string logfile{};
2749 parseCrashdumpParameters(params, filename, timestamp, logfile);
2750
2751 if (filename.empty() || timestamp.empty())
2752 {
2753 messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
2754 return;
2755 }
2756
2757 std::string crashdumpURI =
2758 std::format(
2759 "/redfish/v1/Systems/{}/LogServices/Crashdump/Entries/",
2760 BMCWEB_REDFISH_SYSTEM_URI_NAME) +
2761 logID + "/" + filename;
2762 nlohmann::json::object_t logEntry;
2763 logEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
2764 logEntry["@odata.id"] = boost::urls::format(
2765 "/redfish/v1/Systems/{}/LogServices/Crashdump/Entries/{}",
2766 BMCWEB_REDFISH_SYSTEM_URI_NAME, logID);
2767 logEntry["Name"] = "CPU Crashdump";
2768 logEntry["Id"] = logID;
2769 logEntry["EntryType"] = log_entry::LogEntryType::Oem;
2770 logEntry["AdditionalDataURI"] = std::move(crashdumpURI);
2771 logEntry["DiagnosticDataType"] = "OEM";
2772 logEntry["OEMDiagnosticDataType"] = "PECICrashdump";
2773 logEntry["Created"] = std::move(timestamp);
2774
2775 // If logEntryJson references an array of LogEntry resources
2776 // ('Members' list), then push this as a new entry, otherwise set it
2777 // directly
2778 if (logEntryJson.is_array())
2779 {
2780 logEntryJson.push_back(logEntry);
2781 asyncResp->res.jsonValue["Members@odata.count"] =
2782 logEntryJson.size();
2783 }
2784 else
2785 {
2786 logEntryJson.update(logEntry);
2787 }
2788 };
2789 sdbusplus::asio::getAllProperties(
2790 *crow::connections::systemBus, crashdumpObject,
2791 crashdumpPath + std::string("/") + logID, crashdumpInterface,
2792 std::move(getStoredLogCallback));
2793 }
2794
requestRoutesCrashdumpEntryCollection(App & app)2795 inline void requestRoutesCrashdumpEntryCollection(App& app)
2796 {
2797 // Note: Deviated from redfish privilege registry for GET & HEAD
2798 // method for security reasons.
2799 /**
2800 * Functions triggers appropriate requests on DBus
2801 */
2802 BMCWEB_ROUTE(app,
2803 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/")
2804 // This is incorrect, should be.
2805 //.privileges(redfish::privileges::postLogEntryCollection)
2806 .privileges({{"ConfigureComponents"}})
2807 .methods(
2808 boost::beast::http::verb::
2809 get)([&app](const crow::Request& req,
2810 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2811 const std::string& systemName) {
2812 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2813 {
2814 return;
2815 }
2816 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2817 {
2818 // Option currently returns no systems. TBD
2819 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2820 systemName);
2821 return;
2822 }
2823 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2824 {
2825 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2826 systemName);
2827 return;
2828 }
2829
2830 constexpr std::array<std::string_view, 1> interfaces = {
2831 crashdumpInterface};
2832 dbus::utility::getSubTreePaths(
2833 "/", 0, interfaces,
2834 [asyncResp](const boost::system::error_code& ec,
2835 const std::vector<std::string>& resp) {
2836 if (ec)
2837 {
2838 if (ec.value() !=
2839 boost::system::errc::no_such_file_or_directory)
2840 {
2841 BMCWEB_LOG_DEBUG("failed to get entries ec: {}",
2842 ec.message());
2843 messages::internalError(asyncResp->res);
2844 return;
2845 }
2846 }
2847 asyncResp->res.jsonValue["@odata.type"] =
2848 "#LogEntryCollection.LogEntryCollection";
2849 asyncResp->res.jsonValue["@odata.id"] = std::format(
2850 "/redfish/v1/Systems/{}/LogServices/Crashdump/Entries",
2851 BMCWEB_REDFISH_SYSTEM_URI_NAME);
2852 asyncResp->res.jsonValue["Name"] =
2853 "Open BMC Crashdump Entries";
2854 asyncResp->res.jsonValue["Description"] =
2855 "Collection of Crashdump Entries";
2856 asyncResp->res.jsonValue["Members"] =
2857 nlohmann::json::array();
2858 asyncResp->res.jsonValue["Members@odata.count"] = 0;
2859
2860 for (const std::string& path : resp)
2861 {
2862 const sdbusplus::message::object_path objPath(path);
2863 // Get the log ID
2864 std::string logID = objPath.filename();
2865 if (logID.empty())
2866 {
2867 continue;
2868 }
2869 // Add the log entry to the array
2870 logCrashdumpEntry(asyncResp, logID,
2871 asyncResp->res.jsonValue["Members"]);
2872 }
2873 });
2874 });
2875 }
2876
requestRoutesCrashdumpEntry(App & app)2877 inline void requestRoutesCrashdumpEntry(App& app)
2878 {
2879 // Note: Deviated from redfish privilege registry for GET & HEAD
2880 // method for security reasons.
2881
2882 BMCWEB_ROUTE(
2883 app, "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/<str>/")
2884 // this is incorrect, should be
2885 // .privileges(redfish::privileges::getLogEntry)
2886 .privileges({{"ConfigureComponents"}})
2887 .methods(boost::beast::http::verb::get)(
2888 [&app](const crow::Request& req,
2889 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2890 const std::string& systemName, const std::string& param) {
2891 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2892 {
2893 return;
2894 }
2895 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2896 {
2897 // Option currently returns no systems. TBD
2898 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2899 systemName);
2900 return;
2901 }
2902 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2903 {
2904 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2905 systemName);
2906 return;
2907 }
2908 const std::string& logID = param;
2909 logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue);
2910 });
2911 }
2912
requestRoutesCrashdumpFile(App & app)2913 inline void requestRoutesCrashdumpFile(App& app)
2914 {
2915 // Note: Deviated from redfish privilege registry for GET & HEAD
2916 // method for security reasons.
2917 BMCWEB_ROUTE(
2918 app,
2919 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/<str>/<str>/")
2920 .privileges(redfish::privileges::getLogEntry)
2921 .methods(boost::beast::http::verb::get)(
2922 [](const crow::Request& req,
2923 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2924 const std::string& systemName, const std::string& logID,
2925 const std::string& fileName) {
2926 // Do not call getRedfishRoute here since the crashdump file is
2927 // not a Redfish resource.
2928
2929 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
2930 {
2931 // Option currently returns no systems. TBD
2932 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2933 systemName);
2934 return;
2935 }
2936 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
2937 {
2938 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2939 systemName);
2940 return;
2941 }
2942
2943 auto getStoredLogCallback =
2944 [asyncResp, logID, fileName,
2945 url(boost::urls::url(req.url()))](
2946 const boost::system::error_code& ec,
2947 const std::vector<std::pair<
2948 std::string, dbus::utility::DbusVariantType>>&
2949 resp) {
2950 if (ec)
2951 {
2952 BMCWEB_LOG_DEBUG("failed to get log ec: {}",
2953 ec.message());
2954 messages::internalError(asyncResp->res);
2955 return;
2956 }
2957
2958 std::string dbusFilename{};
2959 std::string dbusTimestamp{};
2960 std::string dbusFilepath{};
2961
2962 parseCrashdumpParameters(resp, dbusFilename,
2963 dbusTimestamp, dbusFilepath);
2964
2965 if (dbusFilename.empty() || dbusTimestamp.empty() ||
2966 dbusFilepath.empty())
2967 {
2968 messages::resourceNotFound(asyncResp->res,
2969 "LogEntry", logID);
2970 return;
2971 }
2972
2973 // Verify the file name parameter is correct
2974 if (fileName != dbusFilename)
2975 {
2976 messages::resourceNotFound(asyncResp->res,
2977 "LogEntry", logID);
2978 return;
2979 }
2980
2981 if (asyncResp->res.openFile(dbusFilepath) !=
2982 crow::OpenCode::Success)
2983 {
2984 messages::resourceNotFound(asyncResp->res,
2985 "LogEntry", logID);
2986 return;
2987 }
2988
2989 // Configure this to be a file download when accessed
2990 // from a browser
2991 asyncResp->res.addHeader(
2992 boost::beast::http::field::content_disposition,
2993 "attachment");
2994 };
2995 sdbusplus::asio::getAllProperties(
2996 *crow::connections::systemBus, crashdumpObject,
2997 crashdumpPath + std::string("/") + logID,
2998 crashdumpInterface, std::move(getStoredLogCallback));
2999 });
3000 }
3001
3002 enum class OEMDiagnosticType
3003 {
3004 onDemand,
3005 telemetry,
3006 invalid,
3007 };
3008
getOEMDiagnosticType(std::string_view oemDiagStr)3009 inline OEMDiagnosticType getOEMDiagnosticType(std::string_view oemDiagStr)
3010 {
3011 if (oemDiagStr == "OnDemand")
3012 {
3013 return OEMDiagnosticType::onDemand;
3014 }
3015 if (oemDiagStr == "Telemetry")
3016 {
3017 return OEMDiagnosticType::telemetry;
3018 }
3019
3020 return OEMDiagnosticType::invalid;
3021 }
3022
requestRoutesCrashdumpCollect(App & app)3023 inline void requestRoutesCrashdumpCollect(App& app)
3024 {
3025 // Note: Deviated from redfish privilege registry for GET & HEAD
3026 // method for security reasons.
3027 BMCWEB_ROUTE(
3028 app,
3029 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData/")
3030 // The below is incorrect; Should be ConfigureManager
3031 //.privileges(redfish::privileges::postLogService)
3032 .privileges({{"ConfigureComponents"}})
3033 .methods(boost::beast::http::verb::post)(
3034 [&app](const crow::Request& req,
3035 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3036 const std::string& systemName) {
3037 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3038 {
3039 return;
3040 }
3041
3042 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
3043 {
3044 // Option currently returns no systems. TBD
3045 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3046 systemName);
3047 return;
3048 }
3049 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
3050 {
3051 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3052 systemName);
3053 return;
3054 }
3055
3056 std::string diagnosticDataType;
3057 std::string oemDiagnosticDataType;
3058 if (!redfish::json_util::readJsonAction(
3059 req, asyncResp->res, "DiagnosticDataType",
3060 diagnosticDataType, "OEMDiagnosticDataType",
3061 oemDiagnosticDataType))
3062 {
3063 return;
3064 }
3065
3066 if (diagnosticDataType != "OEM")
3067 {
3068 BMCWEB_LOG_ERROR(
3069 "Only OEM DiagnosticDataType supported for Crashdump");
3070 messages::actionParameterValueFormatError(
3071 asyncResp->res, diagnosticDataType,
3072 "DiagnosticDataType", "CollectDiagnosticData");
3073 return;
3074 }
3075
3076 OEMDiagnosticType oemDiagType =
3077 getOEMDiagnosticType(oemDiagnosticDataType);
3078
3079 std::string iface;
3080 std::string method;
3081 std::string taskMatchStr;
3082 if (oemDiagType == OEMDiagnosticType::onDemand)
3083 {
3084 iface = crashdumpOnDemandInterface;
3085 method = "GenerateOnDemandLog";
3086 taskMatchStr =
3087 "type='signal',"
3088 "interface='org.freedesktop.DBus.Properties',"
3089 "member='PropertiesChanged',"
3090 "arg0namespace='com.intel.crashdump'";
3091 }
3092 else if (oemDiagType == OEMDiagnosticType::telemetry)
3093 {
3094 iface = crashdumpTelemetryInterface;
3095 method = "GenerateTelemetryLog";
3096 taskMatchStr =
3097 "type='signal',"
3098 "interface='org.freedesktop.DBus.Properties',"
3099 "member='PropertiesChanged',"
3100 "arg0namespace='com.intel.crashdump'";
3101 }
3102 else
3103 {
3104 BMCWEB_LOG_ERROR("Unsupported OEMDiagnosticDataType: {}",
3105 oemDiagnosticDataType);
3106 messages::actionParameterValueFormatError(
3107 asyncResp->res, oemDiagnosticDataType,
3108 "OEMDiagnosticDataType", "CollectDiagnosticData");
3109 return;
3110 }
3111
3112 auto collectCrashdumpCallback =
3113 [asyncResp, payload(task::Payload(req)),
3114 taskMatchStr](const boost::system::error_code& ec,
3115 const std::string&) mutable {
3116 if (ec)
3117 {
3118 if (ec.value() ==
3119 boost::system::errc::operation_not_supported)
3120 {
3121 messages::resourceInStandby(asyncResp->res);
3122 }
3123 else if (ec.value() == boost::system::errc::
3124 device_or_resource_busy)
3125 {
3126 messages::serviceTemporarilyUnavailable(
3127 asyncResp->res, "60");
3128 }
3129 else
3130 {
3131 messages::internalError(asyncResp->res);
3132 }
3133 return;
3134 }
3135 std::shared_ptr<task::TaskData> task =
3136 task::TaskData::createTask(
3137 [](const boost::system::error_code& ec2,
3138 sdbusplus::message_t&,
3139 const std::shared_ptr<task::TaskData>&
3140 taskData) {
3141 if (!ec2)
3142 {
3143 taskData->messages.emplace_back(
3144 messages::taskCompletedOK(
3145 std::to_string(
3146 taskData->index)));
3147 taskData->state = "Completed";
3148 }
3149 return task::completed;
3150 },
3151 taskMatchStr);
3152
3153 task->startTimer(std::chrono::minutes(5));
3154 task->populateResp(asyncResp->res);
3155 task->payload.emplace(std::move(payload));
3156 };
3157
3158 crow::connections::systemBus->async_method_call(
3159 std::move(collectCrashdumpCallback), crashdumpObject,
3160 crashdumpPath, iface, method);
3161 });
3162 }
3163
dBusLogServiceActionsClear(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)3164 inline void dBusLogServiceActionsClear(
3165 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
3166 {
3167 BMCWEB_LOG_DEBUG("Do delete all entries.");
3168
3169 // Process response from Logging service.
3170 auto respHandler = [asyncResp](const boost::system::error_code& ec) {
3171 BMCWEB_LOG_DEBUG("doClearLog resp_handler callback: Done");
3172 if (ec)
3173 {
3174 // TODO Handle for specific error code
3175 BMCWEB_LOG_ERROR("doClearLog resp_handler got error {}", ec);
3176 asyncResp->res.result(
3177 boost::beast::http::status::internal_server_error);
3178 return;
3179 }
3180
3181 asyncResp->res.result(boost::beast::http::status::no_content);
3182 };
3183
3184 // Make call to Logging service to request Clear Log
3185 crow::connections::systemBus->async_method_call(
3186 respHandler, "xyz.openbmc_project.Logging",
3187 "/xyz/openbmc_project/logging",
3188 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
3189 }
3190
3191 /**
3192 * DBusLogServiceActionsClear class supports POST method for ClearLog action.
3193 */
requestRoutesDBusLogServiceActionsClear(App & app)3194 inline void requestRoutesDBusLogServiceActionsClear(App& app)
3195 {
3196 /**
3197 * Function handles POST method request.
3198 * The Clear Log actions does not require any parameter.The action deletes
3199 * all entries found in the Entries collection for this Log Service.
3200 */
3201
3202 BMCWEB_ROUTE(
3203 app,
3204 "/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/")
3205 .privileges(redfish::privileges::postLogService)
3206 .methods(boost::beast::http::verb::post)(
3207 [&app](const crow::Request& req,
3208 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3209 const std::string& systemName) {
3210 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3211 {
3212 return;
3213 }
3214 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
3215 {
3216 // Option currently returns no systems. TBD
3217 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3218 systemName);
3219 return;
3220 }
3221 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
3222 {
3223 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3224 systemName);
3225 return;
3226 }
3227 dBusLogServiceActionsClear(asyncResp);
3228 });
3229 }
3230
3231 } // namespace redfish
3232