xref: /openbmc/bios-bmc-smm-error-logger/src/rde/external_storer_file.cpp (revision bcc6453184916635d4a591e6f8ff278952ebef6d)
137bc0df1Skasunath #include "rde/external_storer_file.hpp"
237bc0df1Skasunath 
337bc0df1Skasunath #include <boost/uuid/uuid.hpp>
437bc0df1Skasunath #include <boost/uuid/uuid_io.hpp>
55de90619SPatrick Williams #include <stdplus/print.hpp>
637bc0df1Skasunath 
75de90619SPatrick Williams #include <format>
837bc0df1Skasunath #include <fstream>
937bc0df1Skasunath #include <string_view>
1037bc0df1Skasunath 
1137bc0df1Skasunath namespace bios_bmc_smm_error_logger
1237bc0df1Skasunath {
1337bc0df1Skasunath namespace rde
1437bc0df1Skasunath {
1537bc0df1Skasunath 
createFolder(const std::string & folderPath) const1637bc0df1Skasunath bool ExternalStorerFileWriter::createFolder(const std::string& folderPath) const
1737bc0df1Skasunath {
1837bc0df1Skasunath     std::filesystem::path path(folderPath);
1937bc0df1Skasunath     if (!std::filesystem::is_directory(path))
2037bc0df1Skasunath     {
21*bcc64531SHao Zhou         stdplus::print(stderr, "no directory at {}, creating.\n", folderPath);
2237bc0df1Skasunath         if (!std::filesystem::create_directories(path))
2337bc0df1Skasunath         {
245de90619SPatrick Williams             stdplus::print(stderr, "Failed to create a folder at {}\n",
255de90619SPatrick Williams                            folderPath);
2637bc0df1Skasunath             return false;
2737bc0df1Skasunath         }
2837bc0df1Skasunath     }
2937bc0df1Skasunath     return true;
3037bc0df1Skasunath }
3137bc0df1Skasunath 
createFile(const std::string & folderPath,const nlohmann::json & jsonPdr) const3237bc0df1Skasunath bool ExternalStorerFileWriter::createFile(const std::string& folderPath,
3337bc0df1Skasunath                                           const nlohmann::json& jsonPdr) const
3437bc0df1Skasunath {
3537bc0df1Skasunath     if (!createFolder(folderPath))
3637bc0df1Skasunath     {
3737bc0df1Skasunath         return false;
3837bc0df1Skasunath     }
3937bc0df1Skasunath     std::filesystem::path path(folderPath);
4037bc0df1Skasunath     path /= "index.json";
4137bc0df1Skasunath     // If the file already exist, overwrite it.
4237bc0df1Skasunath     std::ofstream output(path);
4337bc0df1Skasunath     output << jsonPdr;
4437bc0df1Skasunath     output.close();
4537bc0df1Skasunath     return true;
4637bc0df1Skasunath }
4737bc0df1Skasunath 
removeAll(const std::string & filePath) const4841a58d4fSBrandon Kim bool ExternalStorerFileWriter::removeAll(const std::string& filePath) const
4941a58d4fSBrandon Kim {
5041a58d4fSBrandon Kim     // Attempt to delete the file
5141a58d4fSBrandon Kim     std::error_code ec;
5241a58d4fSBrandon Kim     std::filesystem::remove_all(filePath, ec);
5341a58d4fSBrandon Kim     if (ec)
5441a58d4fSBrandon Kim     {
5541a58d4fSBrandon Kim         return false;
5641a58d4fSBrandon Kim     }
5741a58d4fSBrandon Kim     return true;
5841a58d4fSBrandon Kim }
5941a58d4fSBrandon Kim 
ExternalStorerFileInterface(const std::shared_ptr<sdbusplus::asio::connection> & conn,std::string_view rootPath,std::unique_ptr<FileHandlerInterface> fileHandler,uint32_t numSavedLogEntries,uint32_t numLogEntries)6037bc0df1Skasunath ExternalStorerFileInterface::ExternalStorerFileInterface(
613d0cd556Skasunath     const std::shared_ptr<sdbusplus::asio::connection>& conn,
623d0cd556Skasunath     std::string_view rootPath,
6341a58d4fSBrandon Kim     std::unique_ptr<FileHandlerInterface> fileHandler,
6441a58d4fSBrandon Kim     uint32_t numSavedLogEntries, uint32_t numLogEntries) :
651a64356fSPatrick Williams     rootPath(rootPath), fileHandler(std::move(fileHandler)), logServiceId(""),
6641a58d4fSBrandon Kim     cperNotifier(std::make_unique<CperFileNotifierHandler>(conn)),
6741a58d4fSBrandon Kim     maxNumSavedLogEntries(numSavedLogEntries), maxNumLogEntries(numLogEntries)
6837bc0df1Skasunath {}
6937bc0df1Skasunath 
publishJson(std::string_view jsonStr)7037bc0df1Skasunath bool ExternalStorerFileInterface::publishJson(std::string_view jsonStr)
7137bc0df1Skasunath {
7237bc0df1Skasunath     nlohmann::json jsonDecoded;
7337bc0df1Skasunath     try
7437bc0df1Skasunath     {
7537bc0df1Skasunath         jsonDecoded = nlohmann::json::parse(jsonStr);
7637bc0df1Skasunath     }
7737bc0df1Skasunath     catch (nlohmann::json::parse_error& e)
7837bc0df1Skasunath     {
795de90619SPatrick Williams         stdplus::print(stderr, "JSON parse error: \n{}\n", e.what());
8037bc0df1Skasunath         return false;
8137bc0df1Skasunath     }
8237bc0df1Skasunath 
8337bc0df1Skasunath     // We need to know the type to determine how to process the decoded JSON
8437bc0df1Skasunath     // output.
8537bc0df1Skasunath     if (!jsonDecoded.contains("@odata.type"))
8637bc0df1Skasunath     {
875de90619SPatrick Williams         stdplus::print(stderr, "@odata.type field doesn't exist in:\n {}\n",
8837bc0df1Skasunath                        jsonDecoded.dump(4));
8937bc0df1Skasunath         return false;
9037bc0df1Skasunath     }
9137bc0df1Skasunath 
9237bc0df1Skasunath     auto schemaType = getSchemaType(jsonDecoded);
9337bc0df1Skasunath     if (schemaType == JsonPdrType::logEntry)
9437bc0df1Skasunath     {
9537bc0df1Skasunath         return processLogEntry(jsonDecoded);
9637bc0df1Skasunath     }
9737bc0df1Skasunath     if (schemaType == JsonPdrType::logService)
9837bc0df1Skasunath     {
9937bc0df1Skasunath         return processLogService(jsonDecoded);
10037bc0df1Skasunath     }
10137bc0df1Skasunath     return processOtherTypes(jsonDecoded);
10237bc0df1Skasunath }
10337bc0df1Skasunath 
getSchemaType(const nlohmann::json & jsonSchema) const10437bc0df1Skasunath JsonPdrType ExternalStorerFileInterface::getSchemaType(
10537bc0df1Skasunath     const nlohmann::json& jsonSchema) const
10637bc0df1Skasunath {
10737bc0df1Skasunath     auto logEntryFound =
10837bc0df1Skasunath         std::string(jsonSchema["@odata.type"]).find("LogEntry");
10937bc0df1Skasunath     if (logEntryFound != std::string::npos)
11037bc0df1Skasunath     {
11137bc0df1Skasunath         return JsonPdrType::logEntry;
11237bc0df1Skasunath     }
11337bc0df1Skasunath 
11437bc0df1Skasunath     auto logServiceFound =
11537bc0df1Skasunath         std::string(jsonSchema["@odata.type"]).find("LogService");
11637bc0df1Skasunath     if (logServiceFound != std::string::npos)
11737bc0df1Skasunath     {
11837bc0df1Skasunath         return JsonPdrType::logService;
11937bc0df1Skasunath     }
12037bc0df1Skasunath 
12137bc0df1Skasunath     return JsonPdrType::other;
12237bc0df1Skasunath }
12337bc0df1Skasunath 
processLogEntry(nlohmann::json & logEntry)12437bc0df1Skasunath bool ExternalStorerFileInterface::processLogEntry(nlohmann::json& logEntry)
12537bc0df1Skasunath {
12637bc0df1Skasunath     // TODO: Add policies for LogEntry retention.
12737bc0df1Skasunath     // https://github.com/openbmc/bios-bmc-smm-error-logger/issues/1.
12837bc0df1Skasunath     if (logServiceId.empty())
12937bc0df1Skasunath     {
1305de90619SPatrick Williams         stdplus::print(stderr,
1315de90619SPatrick Williams                        "First need a LogService PDR with a new UUID.\n");
13237bc0df1Skasunath         return false;
13337bc0df1Skasunath     }
13437bc0df1Skasunath 
13541a58d4fSBrandon Kim     // Check to see if we are hitting the limit of filePathQueue, delete oldest
13641a58d4fSBrandon Kim     // log entry first before processing another entry
13741a58d4fSBrandon Kim     if (logEntryQueue.size() == maxNumLogEntries)
13841a58d4fSBrandon Kim     {
13941a58d4fSBrandon Kim         std::string oldestFilePath = std::move(logEntryQueue.front());
14041a58d4fSBrandon Kim         logEntryQueue.pop();
14141a58d4fSBrandon Kim 
14241a58d4fSBrandon Kim         if (!fileHandler->removeAll(oldestFilePath))
14341a58d4fSBrandon Kim         {
14441a58d4fSBrandon Kim             stdplus::print(
14541a58d4fSBrandon Kim                 stderr,
14641a58d4fSBrandon Kim                 "Failed to delete the oldest entry path, not processing the next log,: {}\n",
14741a58d4fSBrandon Kim                 oldestFilePath);
14841a58d4fSBrandon Kim             return false;
14941a58d4fSBrandon Kim         }
15041a58d4fSBrandon Kim     }
15141a58d4fSBrandon Kim 
15237bc0df1Skasunath     std::string id = boost::uuids::to_string(randomGen());
153a3b64fb6Skasunath     std::string fullPath =
1545de90619SPatrick Williams         std::format("{}/redfish/v1/Systems/system/LogServices/{}/Entries/{}",
155a3b64fb6Skasunath                     rootPath, logServiceId, id);
15637bc0df1Skasunath 
15737bc0df1Skasunath     // Populate the "Id" with the UUID we generated.
15837bc0df1Skasunath     logEntry["Id"] = id;
15937bc0df1Skasunath     // Remove the @odata.id from the JSON since ExternalStorer will fill it for
16037bc0df1Skasunath     // a client.
16137bc0df1Skasunath     logEntry.erase("@odata.id");
16237bc0df1Skasunath 
163*bcc64531SHao Zhou     stdplus::print(stderr, "Creating CPER file under path: {}. \n", fullPath);
164a3b64fb6Skasunath     if (!fileHandler->createFile(fullPath, logEntry))
165a3b64fb6Skasunath     {
1665de90619SPatrick Williams         stdplus::print(stderr,
1675de90619SPatrick Williams                        "Failed to create a file for log entry path: {}\n",
168a3b64fb6Skasunath                        fullPath);
169a3b64fb6Skasunath         return false;
170a3b64fb6Skasunath     }
171a3b64fb6Skasunath 
172a3b64fb6Skasunath     cperNotifier->createEntry(fullPath + "/index.json");
17341a58d4fSBrandon Kim 
17441a58d4fSBrandon Kim     // Attempt to push to logEntrySavedQueue first, before pushing to
17541a58d4fSBrandon Kim     // logEntryQueue that can be popped
17641a58d4fSBrandon Kim     if (logEntrySavedQueue.size() < maxNumSavedLogEntries)
17741a58d4fSBrandon Kim     {
17841a58d4fSBrandon Kim         logEntrySavedQueue.push(std::move(fullPath));
17941a58d4fSBrandon Kim     }
18041a58d4fSBrandon Kim     else
18141a58d4fSBrandon Kim     {
18241a58d4fSBrandon Kim         logEntryQueue.push(std::move(fullPath));
18341a58d4fSBrandon Kim     }
18441a58d4fSBrandon Kim 
185a3b64fb6Skasunath     return true;
18637bc0df1Skasunath }
18737bc0df1Skasunath 
processLogService(const nlohmann::json & logService)18837bc0df1Skasunath bool ExternalStorerFileInterface::processLogService(
18937bc0df1Skasunath     const nlohmann::json& logService)
19037bc0df1Skasunath {
19137bc0df1Skasunath     if (!logService.contains("@odata.id"))
19237bc0df1Skasunath     {
1935de90619SPatrick Williams         stdplus::print(stderr, "@odata.id field doesn't exist in:\n {}\n",
19437bc0df1Skasunath                        logService.dump(4));
19537bc0df1Skasunath         return false;
19637bc0df1Skasunath     }
19737bc0df1Skasunath 
19837bc0df1Skasunath     if (!logService.contains("Id"))
19937bc0df1Skasunath     {
2005de90619SPatrick Williams         stdplus::print(stderr, "Id field doesn't exist in:\n {}\n",
20137bc0df1Skasunath                        logService.dump(4));
20237bc0df1Skasunath         return false;
20337bc0df1Skasunath     }
20437bc0df1Skasunath 
20537bc0df1Skasunath     logServiceId = logService["Id"].get<std::string>();
20637bc0df1Skasunath 
20737bc0df1Skasunath     if (!createFile(logService["@odata.id"].get<std::string>(), logService))
20837bc0df1Skasunath     {
2095de90619SPatrick Williams         stdplus::print(stderr,
2105de90619SPatrick Williams                        "Failed to create LogService index file for:\n{}\n",
21137bc0df1Skasunath                        logService.dump(4));
21237bc0df1Skasunath         return false;
21337bc0df1Skasunath     }
21437bc0df1Skasunath     // ExternalStorer needs a .../Entries/index.json file with no data.
21537bc0df1Skasunath     nlohmann::json jEmpty = "{}"_json;
21637bc0df1Skasunath     return createFile(logService["@odata.id"].get<std::string>() + "/Entries",
21737bc0df1Skasunath                       jEmpty);
21837bc0df1Skasunath }
21937bc0df1Skasunath 
processOtherTypes(const nlohmann::json & jsonPdr) const22037bc0df1Skasunath bool ExternalStorerFileInterface::processOtherTypes(
22137bc0df1Skasunath     const nlohmann::json& jsonPdr) const
22237bc0df1Skasunath {
22337bc0df1Skasunath     if (!jsonPdr.contains("@odata.id"))
22437bc0df1Skasunath     {
2255de90619SPatrick Williams         stdplus::print(stderr, "@odata.id field doesn't exist in:\n {}\n",
22637bc0df1Skasunath                        jsonPdr.dump(4));
22737bc0df1Skasunath         return false;
22837bc0df1Skasunath     }
229*bcc64531SHao Zhou 
230*bcc64531SHao Zhou     const std::string& path = jsonPdr["@odata.id"].get<std::string>();
231*bcc64531SHao Zhou 
232*bcc64531SHao Zhou     stdplus::print(stderr,
233*bcc64531SHao Zhou                    "Creating error counter file under path: {}.  content: {}\n",
234*bcc64531SHao Zhou                    path, jsonPdr.dump());
235*bcc64531SHao Zhou     return createFile(path, jsonPdr);
23637bc0df1Skasunath }
23737bc0df1Skasunath 
createFile(const std::string & subPath,const nlohmann::json & jsonPdr) const23837bc0df1Skasunath bool ExternalStorerFileInterface::createFile(
23937bc0df1Skasunath     const std::string& subPath, const nlohmann::json& jsonPdr) const
24037bc0df1Skasunath {
24137bc0df1Skasunath     return fileHandler->createFile(rootPath + subPath, jsonPdr);
24237bc0df1Skasunath }
24337bc0df1Skasunath 
24437bc0df1Skasunath } // namespace rde
24537bc0df1Skasunath } // namespace bios_bmc_smm_error_logger
246