1 #include "rde/external_storer_file.hpp"
2 
3 #include <fmt/format.h>
4 
5 #include <boost/uuid/uuid.hpp>
6 #include <boost/uuid/uuid_io.hpp>
7 
8 #include <fstream>
9 #include <string_view>
10 
11 namespace bios_bmc_smm_error_logger
12 {
13 namespace rde
14 {
15 
16 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             fmt::print(stderr, "Failed to create a folder at {}\n", folderPath);
24             return false;
25         }
26     }
27     return true;
28 }
29 
30 bool ExternalStorerFileWriter::createFile(const std::string& folderPath,
31                                           const nlohmann::json& jsonPdr) const
32 {
33     if (!createFolder(folderPath))
34     {
35         return false;
36     }
37     std::filesystem::path path(folderPath);
38     path /= "index.json";
39     // If the file already exist, overwrite it.
40     std::ofstream output(path);
41     output << jsonPdr;
42     output.close();
43     return true;
44 }
45 
46 ExternalStorerFileInterface::ExternalStorerFileInterface(
47     sdbusplus::bus_t& bus, std::string_view rootPath,
48     std::unique_ptr<FileHandlerInterface> fileHandler) :
49     rootPath(rootPath),
50     fileHandler(std::move(fileHandler)), logServiceId(""),
51     cperNotifier(std::make_unique<CperFileNotifierHandler>(bus))
52 {}
53 
54 bool ExternalStorerFileInterface::publishJson(std::string_view jsonStr)
55 {
56     nlohmann::json jsonDecoded;
57     try
58     {
59         jsonDecoded = nlohmann::json::parse(jsonStr);
60     }
61     catch (nlohmann::json::parse_error& e)
62     {
63         fmt::print(stderr, "JSON parse error: \n{}\n", e.what());
64         return false;
65     }
66 
67     // We need to know the type to determine how to process the decoded JSON
68     // output.
69     if (!jsonDecoded.contains("@odata.type"))
70     {
71         fmt::print(stderr, "@odata.type field doesn't exist in:\n {}\n",
72                    jsonDecoded.dump(4));
73         return false;
74     }
75 
76     auto schemaType = getSchemaType(jsonDecoded);
77     if (schemaType == JsonPdrType::logEntry)
78     {
79         return processLogEntry(jsonDecoded);
80     }
81     if (schemaType == JsonPdrType::logService)
82     {
83         return processLogService(jsonDecoded);
84     }
85     return processOtherTypes(jsonDecoded);
86 }
87 
88 JsonPdrType ExternalStorerFileInterface::getSchemaType(
89     const nlohmann::json& jsonSchema) const
90 {
91     auto logEntryFound =
92         std::string(jsonSchema["@odata.type"]).find("LogEntry");
93     if (logEntryFound != std::string::npos)
94     {
95         return JsonPdrType::logEntry;
96     }
97 
98     auto logServiceFound =
99         std::string(jsonSchema["@odata.type"]).find("LogService");
100     if (logServiceFound != std::string::npos)
101     {
102         return JsonPdrType::logService;
103     }
104 
105     return JsonPdrType::other;
106 }
107 
108 bool ExternalStorerFileInterface::processLogEntry(nlohmann::json& logEntry)
109 {
110     // TODO: Add policies for LogEntry retention.
111     // https://github.com/openbmc/bios-bmc-smm-error-logger/issues/1.
112     if (logServiceId.empty())
113     {
114         fmt::print(stderr, "First need a LogService PDR with a new UUID.\n");
115         return false;
116     }
117 
118     std::string id = boost::uuids::to_string(randomGen());
119     std::string fullPath =
120         fmt::format("{}/redfish/v1/Systems/system/LogServices/{}/Entries/{}",
121                     rootPath, logServiceId, id);
122 
123     // Populate the "Id" with the UUID we generated.
124     logEntry["Id"] = id;
125     // Remove the @odata.id from the JSON since ExternalStorer will fill it for
126     // a client.
127     logEntry.erase("@odata.id");
128 
129     if (!fileHandler->createFile(fullPath, logEntry))
130     {
131         fmt::print(stderr, "Failed to create a file for log entry path: {}\n",
132                    fullPath);
133         return false;
134     }
135 
136     cperNotifier->createEntry(fullPath + "/index.json");
137     return true;
138 }
139 
140 bool ExternalStorerFileInterface::processLogService(
141     const nlohmann::json& logService)
142 {
143     if (!logService.contains("@odata.id"))
144     {
145         fmt::print(stderr, "@odata.id field doesn't exist in:\n {}\n",
146                    logService.dump(4));
147         return false;
148     }
149 
150     if (!logService.contains("Id"))
151     {
152         fmt::print(stderr, "Id field doesn't exist in:\n {}\n",
153                    logService.dump(4));
154         return false;
155     }
156 
157     logServiceId = logService["Id"].get<std::string>();
158 
159     if (!createFile(logService["@odata.id"].get<std::string>(), logService))
160     {
161         fmt::print(stderr, "Failed to create LogService index file for:\n{}\n",
162                    logService.dump(4));
163         return false;
164     }
165     // ExternalStorer needs a .../Entries/index.json file with no data.
166     nlohmann::json jEmpty = "{}"_json;
167     return createFile(logService["@odata.id"].get<std::string>() + "/Entries",
168                       jEmpty);
169 }
170 
171 bool ExternalStorerFileInterface::processOtherTypes(
172     const nlohmann::json& jsonPdr) const
173 {
174     if (!jsonPdr.contains("@odata.id"))
175     {
176         fmt::print(stderr, "@odata.id field doesn't exist in:\n {}\n",
177                    jsonPdr.dump(4));
178         return false;
179     }
180     return createFile(jsonPdr["@odata.id"].get<std::string>(), jsonPdr);
181 }
182 
183 bool ExternalStorerFileInterface::createFile(
184     const std::string& subPath, const nlohmann::json& jsonPdr) const
185 {
186     return fileHandler->createFile(rootPath + subPath, jsonPdr);
187 }
188 
189 } // namespace rde
190 } // namespace bios_bmc_smm_error_logger
191