1 #include "file_io_type_pel.hpp"
2 
3 #include "common/utils.hpp"
4 #include "xyz/openbmc_project/Common/error.hpp"
5 
6 #include <libpldm/base.h>
7 #include <libpldm/file_io.h>
8 #include <stdint.h>
9 #include <systemd/sd-bus.h>
10 #include <unistd.h>
11 
12 #include <phosphor-logging/lg2.hpp>
13 #include <sdbusplus/server.hpp>
14 #include <xyz/openbmc_project/Logging/Entry/server.hpp>
15 
16 #include <exception>
17 #include <filesystem>
18 #include <fstream>
19 #include <iostream>
20 #include <vector>
21 
22 PHOSPHOR_LOG2_USING;
23 
24 namespace pldm
25 {
26 namespace responder
27 {
28 using namespace sdbusplus::xyz::openbmc_project::Logging::server;
29 
30 namespace detail
31 {
32 /**
33  * @brief Finds the Entry::Level value for the severity of the PEL
34  *        passed in.
35  *
36  * The severity byte is at offset 10 in the User Header section,
37  * which is always after the 48 byte Private Header section.
38  *
39  * @param[in] pelFileName - The file containing the PEL
40  *
41  * @return Entry::Level - The severity value for the Entry
42  */
43 Entry::Level getEntryLevelFromPEL(const std::string& pelFileName)
44 {
45     const std::map<uint8_t, Entry::Level> severityMap{
46         {0x00, Entry::Level::Informational}, // Informational event
47         {0x10, Entry::Level::Warning},       // Recoverable error
48         {0x20, Entry::Level::Warning},       // Predictive error
49         {0x40, Entry::Level::Error},         // Unrecoverable error
50         {0x50, Entry::Level::Error},         // Critical error
51         {0x60, Entry::Level::Error},         // Error from a diagnostic test
52         {0x70, Entry::Level::Warning}        // Recoverable symptom
53     };
54 
55     const size_t severityOffset = 0x3A;
56 
57     size_t size = 0;
58     if (fs::exists(pelFileName))
59     {
60         size = fs::file_size(pelFileName);
61     }
62 
63     if (size > severityOffset)
64     {
65         std::ifstream pel{pelFileName};
66         if (pel.good())
67         {
68             pel.seekg(severityOffset);
69 
70             uint8_t sev;
71             pel.read(reinterpret_cast<char*>(&sev), 1);
72 
73             // Get the type
74             sev = sev & 0xF0;
75 
76             auto entry = severityMap.find(sev);
77             if (entry != severityMap.end())
78             {
79                 return entry->second;
80             }
81         }
82         else
83         {
84             error("Unable to open PEL file {PEL_FILE_NAME}", "PEL_FILE_NAME",
85                   pelFileName);
86         }
87     }
88 
89     return Entry::Level::Error;
90 }
91 } // namespace detail
92 
93 int PelHandler::readIntoMemory(uint32_t offset, uint32_t& length,
94                                uint64_t address,
95                                oem_platform::Handler* /*oemPlatformHandler*/)
96 {
97     static constexpr auto logObjPath = "/xyz/openbmc_project/logging";
98     static constexpr auto logInterface = "org.open_power.Logging.PEL";
99 
100     auto& bus = pldm::utils::DBusHandler::getBus();
101 
102     try
103     {
104         auto service = pldm::utils::DBusHandler().getService(logObjPath,
105                                                              logInterface);
106         auto method = bus.new_method_call(service.c_str(), logObjPath,
107                                           logInterface, "GetPEL");
108         method.append(fileHandle);
109         auto reply = bus.call(
110             method,
111             std::chrono::duration_cast<microsec>(sec(DBUS_TIMEOUT)).count());
112         sdbusplus::message::unix_fd fd{};
113         reply.read(fd);
114         auto rc = transferFileData(fd, true, offset, length, address);
115         return rc;
116     }
117     catch (const std::exception& e)
118     {
119         error(
120             "GetPEL D-Bus call failed, PEL id = 0x{FILE_HANDLE}, error ={ERR_EXCEP}",
121             "FILE_HANDLE", lg2::hex, fileHandle, "ERR_EXCEP", e.what());
122         return PLDM_ERROR;
123     }
124 
125     return PLDM_SUCCESS;
126 }
127 
128 int PelHandler::read(uint32_t offset, uint32_t& length, Response& response,
129                      oem_platform::Handler* /*oemPlatformHandler*/)
130 {
131     static constexpr auto logObjPath = "/xyz/openbmc_project/logging";
132     static constexpr auto logInterface = "org.open_power.Logging.PEL";
133     auto& bus = pldm::utils::DBusHandler::getBus();
134 
135     try
136     {
137         auto service = pldm::utils::DBusHandler().getService(logObjPath,
138                                                              logInterface);
139         auto method = bus.new_method_call(service.c_str(), logObjPath,
140                                           logInterface, "GetPEL");
141         method.append(fileHandle);
142         auto reply = bus.call(
143             method,
144             std::chrono::duration_cast<microsec>(sec(DBUS_TIMEOUT)).count());
145         sdbusplus::message::unix_fd fd{};
146         reply.read(fd);
147 
148         off_t fileSize = lseek(fd, 0, SEEK_END);
149         if (fileSize == -1)
150         {
151             error("file seek failed");
152             return PLDM_ERROR;
153         }
154         if (offset >= fileSize)
155         {
156             error(
157                 "Offset exceeds file size, OFFSET={OFFSET} FILE_SIZE={FILE_SIZE}",
158                 "OFFSET", offset, "FILE_SIZE", fileSize);
159             return PLDM_DATA_OUT_OF_RANGE;
160         }
161         if (offset + length > fileSize)
162         {
163             length = fileSize - offset;
164         }
165         auto rc = lseek(fd, offset, SEEK_SET);
166         if (rc == -1)
167         {
168             error("file seek failed");
169             return PLDM_ERROR;
170         }
171         size_t currSize = response.size();
172         response.resize(currSize + length);
173         auto filePos = reinterpret_cast<char*>(response.data());
174         filePos += currSize;
175         rc = ::read(fd, filePos, length);
176         if (rc == -1)
177         {
178             error("file read failed");
179             return PLDM_ERROR;
180         }
181         if (rc != length)
182         {
183             error(
184                 "mismatch between number of characters to read and the length read, LENGTH={LEN} COUNT={CNT}",
185                 "LEN", length, "CNT", rc);
186             return PLDM_ERROR;
187         }
188     }
189     catch (const std::exception& e)
190     {
191         error(
192             "GetPEL D-Bus call failed on PEL ID 0x{FILE_HANDLE}, error ={ERR_EXCEP}",
193             "FILE_HANDLE", lg2::hex, fileHandle, "ERR_EXCEP", e.what());
194         return PLDM_ERROR;
195     }
196     return PLDM_SUCCESS;
197 }
198 
199 int PelHandler::writeFromMemory(uint32_t offset, uint32_t length,
200                                 uint64_t address,
201                                 oem_platform::Handler* /*oemPlatformHandler*/)
202 {
203     char tmpFile[] = "/tmp/pel.XXXXXX";
204     int fd = mkstemp(tmpFile);
205     if (fd == -1)
206     {
207         error("failed to create a temporary pel, ERROR={ERR}", "ERR", errno);
208         return PLDM_ERROR;
209     }
210     close(fd);
211     fs::path path(tmpFile);
212 
213     auto rc = transferFileData(path, false, offset, length, address);
214     if (rc == PLDM_SUCCESS)
215     {
216         rc = storePel(path.string());
217     }
218     return rc;
219 }
220 
221 int PelHandler::fileAck(uint8_t /*fileStatus*/)
222 {
223     static constexpr auto logObjPath = "/xyz/openbmc_project/logging";
224     static constexpr auto logInterface = "org.open_power.Logging.PEL";
225     auto& bus = pldm::utils::DBusHandler::getBus();
226 
227     try
228     {
229         auto service = pldm::utils::DBusHandler().getService(logObjPath,
230                                                              logInterface);
231         auto method = bus.new_method_call(service.c_str(), logObjPath,
232                                           logInterface, "HostAck");
233         method.append(fileHandle);
234         bus.call_noreply(method);
235     }
236     catch (const std::exception& e)
237     {
238         error(
239             "HostAck D-Bus call failed on PEL ID 0x{FILE_HANDLE}, error ={ERR_EXCEP}",
240             "FILE_HANDLE", lg2::hex, fileHandle, "ERR_EXCEP", e.what());
241         return PLDM_ERROR;
242     }
243 
244     return PLDM_SUCCESS;
245 }
246 
247 int PelHandler::storePel(std::string&& pelFileName)
248 {
249     static constexpr auto logObjPath = "/xyz/openbmc_project/logging";
250     static constexpr auto logInterface = "xyz.openbmc_project.Logging.Create";
251 
252     auto& bus = pldm::utils::DBusHandler::getBus();
253 
254     try
255     {
256         auto service = pldm::utils::DBusHandler().getService(logObjPath,
257                                                              logInterface);
258         using namespace sdbusplus::xyz::openbmc_project::Logging::server;
259         std::map<std::string, std::string> addlData{};
260         auto severity =
261             sdbusplus::xyz::openbmc_project::Logging::server::convertForMessage(
262                 detail::getEntryLevelFromPEL(pelFileName));
263         addlData.emplace("RAWPEL", std::move(pelFileName));
264 
265         auto method = bus.new_method_call(service.c_str(), logObjPath,
266                                           logInterface, "Create");
267         method.append("xyz.openbmc_project.Host.Error.Event", severity,
268                       addlData);
269         bus.call_noreply(method);
270     }
271     catch (const std::exception& e)
272     {
273         error("failed to make a d-bus call to PEL daemon, ERROR={ERR_EXCEP}",
274               "ERR_EXCEP", e.what());
275         return PLDM_ERROR;
276     }
277 
278     return PLDM_SUCCESS;
279 }
280 
281 int PelHandler::write(const char* buffer, uint32_t offset, uint32_t& length,
282                       oem_platform::Handler* /*oemPlatformHandler*/)
283 {
284     int rc = PLDM_SUCCESS;
285 
286     if (offset > 0)
287     {
288         error("Offset is non zero");
289         return PLDM_ERROR;
290     }
291 
292     char tmpFile[] = "/tmp/pel.XXXXXX";
293     auto fd = mkstemp(tmpFile);
294     if (fd == -1)
295     {
296         error("failed to create a temporary pel, ERROR={ERR}", "ERR", errno);
297         return PLDM_ERROR;
298     }
299 
300     size_t written = 0;
301     do
302     {
303         if ((rc = ::write(fd, buffer, length - written)) == -1)
304         {
305             break;
306         }
307         written += rc;
308         buffer += rc;
309     } while (rc && written < length);
310     close(fd);
311 
312     if (rc == -1)
313     {
314         error("file write failed, ERROR={ERR}, LENGTH={LEN}, OFFSET={OFFSET}",
315               "ERR", errno, "LEN", length, "OFFSET", offset);
316         fs::remove(tmpFile);
317         return PLDM_ERROR;
318     }
319 
320     if (written == length)
321     {
322         fs::path path(tmpFile);
323         rc = storePel(path.string());
324         if (rc != PLDM_SUCCESS)
325         {
326             error("save PEL failed, ERROR = {RC} tmpFile = {TMP_FILE}", "RC",
327                   rc, "TMP_FILE", tmpFile);
328         }
329     }
330 
331     return rc;
332 }
333 
334 } // namespace responder
335 } // namespace pldm
336