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