xref: /openbmc/bios-bmc-smm-error-logger/src/rde/external_storer_file.cpp (revision bcc6453184916635d4a591e6f8ff278952ebef6d)
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