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