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