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