1 #include "rde/external_storer_file.hpp"
2
3 #include <boost/uuid/uuid.hpp>
4 #include <boost/uuid/uuid_io.hpp>
5 #include <stdplus/print.hpp>
6
7 #include <format>
8 #include <fstream>
9 #include <string_view>
10
11 namespace bios_bmc_smm_error_logger
12 {
13 namespace rde
14 {
15
createFolder(const std::string & folderPath) const16 bool ExternalStorerFileWriter::createFolder(const std::string& folderPath) const
17 {
18 std::filesystem::path path(folderPath);
19 if (!std::filesystem::is_directory(path))
20 {
21 stdplus::print(stderr, "no directory at {}, creating.\n", folderPath);
22 if (!std::filesystem::create_directories(path))
23 {
24 stdplus::print(stderr, "Failed to create a folder at {}\n",
25 folderPath);
26 return false;
27 }
28 }
29 return true;
30 }
31
createFile(const std::string & folderPath,const nlohmann::json & jsonPdr) const32 bool ExternalStorerFileWriter::createFile(const std::string& folderPath,
33 const nlohmann::json& jsonPdr) const
34 {
35 if (!createFolder(folderPath))
36 {
37 return false;
38 }
39 std::filesystem::path path(folderPath);
40 path /= "index.json";
41 // If the file already exist, overwrite it.
42 std::ofstream output(path);
43 output << jsonPdr;
44 output.close();
45 return true;
46 }
47
removeAll(const std::string & filePath) const48 bool ExternalStorerFileWriter::removeAll(const std::string& filePath) const
49 {
50 // Attempt to delete the file
51 std::error_code ec;
52 std::filesystem::remove_all(filePath, ec);
53 if (ec)
54 {
55 return false;
56 }
57 return true;
58 }
59
ExternalStorerFileInterface(const std::shared_ptr<sdbusplus::asio::connection> & conn,std::string_view rootPath,std::unique_ptr<FileHandlerInterface> fileHandler,uint32_t numSavedLogEntries,uint32_t numLogEntries)60 ExternalStorerFileInterface::ExternalStorerFileInterface(
61 const std::shared_ptr<sdbusplus::asio::connection>& conn,
62 std::string_view rootPath,
63 std::unique_ptr<FileHandlerInterface> fileHandler,
64 uint32_t numSavedLogEntries, uint32_t numLogEntries) :
65 rootPath(rootPath), fileHandler(std::move(fileHandler)), logServiceId(""),
66 cperNotifier(std::make_unique<CperFileNotifierHandler>(conn)),
67 maxNumSavedLogEntries(numSavedLogEntries), maxNumLogEntries(numLogEntries)
68 {}
69
publishJson(std::string_view jsonStr)70 bool ExternalStorerFileInterface::publishJson(std::string_view jsonStr)
71 {
72 nlohmann::json jsonDecoded;
73 try
74 {
75 jsonDecoded = nlohmann::json::parse(jsonStr);
76 }
77 catch (nlohmann::json::parse_error& e)
78 {
79 stdplus::print(stderr, "JSON parse error: \n{}\n", e.what());
80 return false;
81 }
82
83 // We need to know the type to determine how to process the decoded JSON
84 // output.
85 if (!jsonDecoded.contains("@odata.type"))
86 {
87 stdplus::print(stderr, "@odata.type field doesn't exist in:\n {}\n",
88 jsonDecoded.dump(4));
89 return false;
90 }
91
92 auto schemaType = getSchemaType(jsonDecoded);
93 if (schemaType == JsonPdrType::logEntry)
94 {
95 return processLogEntry(jsonDecoded);
96 }
97 if (schemaType == JsonPdrType::logService)
98 {
99 return processLogService(jsonDecoded);
100 }
101 return processOtherTypes(jsonDecoded);
102 }
103
getSchemaType(const nlohmann::json & jsonSchema) const104 JsonPdrType ExternalStorerFileInterface::getSchemaType(
105 const nlohmann::json& jsonSchema) const
106 {
107 auto logEntryFound =
108 std::string(jsonSchema["@odata.type"]).find("LogEntry");
109 if (logEntryFound != std::string::npos)
110 {
111 return JsonPdrType::logEntry;
112 }
113
114 auto logServiceFound =
115 std::string(jsonSchema["@odata.type"]).find("LogService");
116 if (logServiceFound != std::string::npos)
117 {
118 return JsonPdrType::logService;
119 }
120
121 return JsonPdrType::other;
122 }
123
processLogEntry(nlohmann::json & logEntry)124 bool ExternalStorerFileInterface::processLogEntry(nlohmann::json& logEntry)
125 {
126 // TODO: Add policies for LogEntry retention.
127 // https://github.com/openbmc/bios-bmc-smm-error-logger/issues/1.
128 if (logServiceId.empty())
129 {
130 stdplus::print(stderr,
131 "First need a LogService PDR with a new UUID.\n");
132 return false;
133 }
134
135 // Check to see if we are hitting the limit of filePathQueue, delete oldest
136 // log entry first before processing another entry
137 if (logEntryQueue.size() == maxNumLogEntries)
138 {
139 std::string oldestFilePath = std::move(logEntryQueue.front());
140 logEntryQueue.pop();
141
142 if (!fileHandler->removeAll(oldestFilePath))
143 {
144 stdplus::print(
145 stderr,
146 "Failed to delete the oldest entry path, not processing the next log,: {}\n",
147 oldestFilePath);
148 return false;
149 }
150 }
151
152 std::string id = boost::uuids::to_string(randomGen());
153 std::string fullPath =
154 std::format("{}/redfish/v1/Systems/system/LogServices/{}/Entries/{}",
155 rootPath, logServiceId, id);
156
157 // Populate the "Id" with the UUID we generated.
158 logEntry["Id"] = id;
159 // Remove the @odata.id from the JSON since ExternalStorer will fill it for
160 // a client.
161 logEntry.erase("@odata.id");
162
163 stdplus::print(stderr, "Creating CPER file under path: {}. \n", fullPath);
164 if (!fileHandler->createFile(fullPath, logEntry))
165 {
166 stdplus::print(stderr,
167 "Failed to create a file for log entry path: {}\n",
168 fullPath);
169 return false;
170 }
171
172 cperNotifier->createEntry(fullPath + "/index.json");
173
174 // Attempt to push to logEntrySavedQueue first, before pushing to
175 // logEntryQueue that can be popped
176 if (logEntrySavedQueue.size() < maxNumSavedLogEntries)
177 {
178 logEntrySavedQueue.push(std::move(fullPath));
179 }
180 else
181 {
182 logEntryQueue.push(std::move(fullPath));
183 }
184
185 return true;
186 }
187
processLogService(const nlohmann::json & logService)188 bool ExternalStorerFileInterface::processLogService(
189 const nlohmann::json& logService)
190 {
191 if (!logService.contains("@odata.id"))
192 {
193 stdplus::print(stderr, "@odata.id field doesn't exist in:\n {}\n",
194 logService.dump(4));
195 return false;
196 }
197
198 if (!logService.contains("Id"))
199 {
200 stdplus::print(stderr, "Id field doesn't exist in:\n {}\n",
201 logService.dump(4));
202 return false;
203 }
204
205 logServiceId = logService["Id"].get<std::string>();
206
207 if (!createFile(logService["@odata.id"].get<std::string>(), logService))
208 {
209 stdplus::print(stderr,
210 "Failed to create LogService index file for:\n{}\n",
211 logService.dump(4));
212 return false;
213 }
214 // ExternalStorer needs a .../Entries/index.json file with no data.
215 nlohmann::json jEmpty = "{}"_json;
216 return createFile(logService["@odata.id"].get<std::string>() + "/Entries",
217 jEmpty);
218 }
219
processOtherTypes(const nlohmann::json & jsonPdr) const220 bool ExternalStorerFileInterface::processOtherTypes(
221 const nlohmann::json& jsonPdr) const
222 {
223 if (!jsonPdr.contains("@odata.id"))
224 {
225 stdplus::print(stderr, "@odata.id field doesn't exist in:\n {}\n",
226 jsonPdr.dump(4));
227 return false;
228 }
229
230 const std::string& path = jsonPdr["@odata.id"].get<std::string>();
231
232 stdplus::print(stderr,
233 "Creating error counter file under path: {}. content: {}\n",
234 path, jsonPdr.dump());
235 return createFile(path, jsonPdr);
236 }
237
createFile(const std::string & subPath,const nlohmann::json & jsonPdr) const238 bool ExternalStorerFileInterface::createFile(
239 const std::string& subPath, const nlohmann::json& jsonPdr) const
240 {
241 return fileHandler->createFile(rootPath + subPath, jsonPdr);
242 }
243
244 } // namespace rde
245 } // namespace bios_bmc_smm_error_logger
246