1 #pragma once
2 
3 #include "app.hpp"
4 #include "generated/enums/log_entry.hpp"
5 #include "query.hpp"
6 #include "registries/openbmc_message_registry.hpp"
7 #include "registries/privilege_registry.hpp"
8 #include "utils/time_utils.hpp"
9 
10 #include <cstdint>
11 #include <memory>
12 #include <string_view>
13 #include <utility>
14 #include <vector>
15 
16 namespace redfish
17 {
18 
19 inline void fillHostLoggerEntryJson(std::string_view logEntryID,
20                                     std::string_view msg,
21                                     nlohmann::json::object_t& logEntryJson)
22 {
23     // Fill in the log entry with the gathered data.
24     logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
25     logEntryJson["@odata.id"] = boost::urls::format(
26         "/redfish/v1/Systems/{}/LogServices/HostLogger/Entries/{}",
27         BMCWEB_REDFISH_SYSTEM_URI_NAME, logEntryID);
28     logEntryJson["Name"] = "Host Logger Entry";
29     logEntryJson["Id"] = logEntryID;
30     logEntryJson["Message"] = msg;
31     logEntryJson["EntryType"] = log_entry::LogEntryType::Oem;
32     logEntryJson["Severity"] = log_entry::EventSeverity::OK;
33     logEntryJson["OemRecordFormat"] = "Host Logger Entry";
34 }
35 
36 inline void handleSystemsLogServicesHostloggerGet(
37     App& app, const crow::Request& req,
38     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
39     const std::string& systemName)
40 {
41     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
42     {
43         return;
44     }
45     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
46     {
47         // Option currently returns no systems.  TBD
48         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
49                                    systemName);
50         return;
51     }
52     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
53     {
54         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
55                                    systemName);
56         return;
57     }
58     asyncResp->res.jsonValue["@odata.id"] =
59         std::format("/redfish/v1/Systems/{}/LogServices/HostLogger",
60                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
61     asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
62     asyncResp->res.jsonValue["Name"] = "Host Logger Service";
63     asyncResp->res.jsonValue["Description"] = "Host Logger Service";
64     asyncResp->res.jsonValue["Id"] = "HostLogger";
65     asyncResp->res.jsonValue["Entries"]["@odata.id"] =
66         std::format("/redfish/v1/Systems/{}/LogServices/HostLogger/Entries",
67                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
68 }
69 
70 inline void handleSystemsLogServicesHostloggerEntriesGet(
71     App& app, const crow::Request& req,
72     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
73     const std::string& systemName)
74 {
75     query_param::QueryCapabilities capabilities = {
76         .canDelegateTop = true,
77         .canDelegateSkip = true,
78     };
79     query_param::Query delegatedQuery;
80     if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
81                                                   delegatedQuery, capabilities))
82     {
83         return;
84     }
85     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
86     {
87         // Option currently returns no systems.  TBD
88         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
89                                    systemName);
90         return;
91     }
92     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
93     {
94         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
95                                    systemName);
96         return;
97     }
98     asyncResp->res.jsonValue["@odata.id"] =
99         std::format("/redfish/v1/Systems/{}/LogServices/HostLogger/Entries",
100                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
101     asyncResp->res.jsonValue["@odata.type"] =
102         "#LogEntryCollection.LogEntryCollection";
103     asyncResp->res.jsonValue["Name"] = "HostLogger Entries";
104     asyncResp->res.jsonValue["Description"] =
105         "Collection of HostLogger Entries";
106     nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
107     logEntryArray = nlohmann::json::array();
108     asyncResp->res.jsonValue["Members@odata.count"] = 0;
109 
110     std::vector<std::filesystem::path> hostLoggerFiles;
111     if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles))
112     {
113         BMCWEB_LOG_DEBUG("Failed to get host log file path");
114         return;
115     }
116     // If we weren't provided top and skip limits, use the defaults.
117     size_t skip = delegatedQuery.skip.value_or(0);
118     size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
119     size_t logCount = 0;
120     // This vector only store the entries we want to expose that
121     // control by skip and top.
122     std::vector<std::string> logEntries;
123     if (!getHostLoggerEntries(hostLoggerFiles, skip, top, logEntries, logCount))
124     {
125         messages::internalError(asyncResp->res);
126         return;
127     }
128     // If vector is empty, that means skip value larger than total
129     // log count
130     if (logEntries.empty())
131     {
132         asyncResp->res.jsonValue["Members@odata.count"] = logCount;
133         return;
134     }
135     if (!logEntries.empty())
136     {
137         for (size_t i = 0; i < logEntries.size(); i++)
138         {
139             nlohmann::json::object_t hostLogEntry;
140             fillHostLoggerEntryJson(std::to_string(skip + i), logEntries[i],
141                                     hostLogEntry);
142             logEntryArray.emplace_back(std::move(hostLogEntry));
143         }
144 
145         asyncResp->res.jsonValue["Members@odata.count"] = logCount;
146         if (skip + top < logCount)
147         {
148             asyncResp->res.jsonValue["Members@odata.nextLink"] =
149                 std::format(
150                     "/redfish/v1/Systems/{}/LogServices/HostLogger/Entries?$skip=",
151                     BMCWEB_REDFISH_SYSTEM_URI_NAME) +
152                 std::to_string(skip + top);
153         }
154     }
155 }
156 
157 inline void handleSystemsLogServicesHostloggerEntriesEntryGet(
158     App& app, const crow::Request& req,
159     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
160     const std::string& systemName, const std::string& param)
161 {
162     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
163     {
164         return;
165     }
166     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
167     {
168         // Option currently returns no systems.  TBD
169         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
170                                    systemName);
171         return;
172     }
173     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
174     {
175         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
176                                    systemName);
177         return;
178     }
179     std::string_view targetID = param;
180 
181     uint64_t idInt = 0;
182 
183     auto [ptr, ec] = std::from_chars(targetID.begin(), targetID.end(), idInt);
184     if (ec != std::errc{} || ptr != targetID.end())
185     {
186         messages::resourceNotFound(asyncResp->res, "LogEntry", param);
187         return;
188     }
189 
190     std::vector<std::filesystem::path> hostLoggerFiles;
191     if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles))
192     {
193         BMCWEB_LOG_DEBUG("Failed to get host log file path");
194         return;
195     }
196 
197     size_t logCount = 0;
198     size_t top = 1;
199     std::vector<std::string> logEntries;
200     // We can get specific entry by skip and top. For example, if we
201     // want to get nth entry, we can set skip = n-1 and top = 1 to
202     // get that entry
203     if (!getHostLoggerEntries(hostLoggerFiles, idInt, top, logEntries,
204                               logCount))
205     {
206         messages::internalError(asyncResp->res);
207         return;
208     }
209 
210     if (!logEntries.empty())
211     {
212         nlohmann::json::object_t hostLogEntry;
213         fillHostLoggerEntryJson(targetID, logEntries[0], hostLogEntry);
214         asyncResp->res.jsonValue.update(hostLogEntry);
215         return;
216     }
217 
218     // Requested ID was not found
219     messages::resourceNotFound(asyncResp->res, "LogEntry", param);
220 }
221 
222 inline void requestRoutesSystemsLogServiceHostlogger(App& app)
223 {
224     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/")
225         .privileges(redfish::privileges::getLogService)
226         .methods(boost::beast::http::verb::get)(std::bind_front(
227             handleSystemsLogServicesHostloggerGet, std::ref(app)));
228     BMCWEB_ROUTE(app,
229                  "/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/")
230         .privileges(redfish::privileges::getLogEntryCollection)
231         .methods(boost::beast::http::verb::get)(std::bind_front(
232             handleSystemsLogServicesHostloggerEntriesGet, std::ref(app)));
233 
234     BMCWEB_ROUTE(
235         app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/<str>/")
236         .privileges(redfish::privileges::getLogEntry)
237         .methods(boost::beast::http::verb::get)(std::bind_front(
238             handleSystemsLogServicesHostloggerEntriesEntryGet, std::ref(app)));
239 }
240 
241 } // namespace redfish
242