1 /**
2  * Copyright © 2019 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "manager.hpp"
17 
18 #include "additional_data.hpp"
19 #include "json_utils.hpp"
20 #include "pel.hpp"
21 
22 #include <unistd.h>
23 
24 #include <filesystem>
25 #include <fstream>
26 #include <xyz/openbmc_project/Common/error.hpp>
27 
28 namespace openpower
29 {
30 namespace pels
31 {
32 
33 using namespace phosphor::logging;
34 namespace fs = std::filesystem;
35 namespace rg = openpower::pels::message;
36 
37 namespace common_error = sdbusplus::xyz::openbmc_project::Common::Error;
38 
39 namespace additional_data
40 {
41 constexpr auto rawPEL = "RAWPEL";
42 constexpr auto esel = "ESEL";
43 } // namespace additional_data
44 
45 void Manager::create(const std::string& message, uint32_t obmcLogID,
46                      uint64_t timestamp, Entry::Level severity,
47                      const std::vector<std::string>& additionalData,
48                      const std::vector<std::string>& associations)
49 {
50     AdditionalData ad{additionalData};
51 
52     // If a PEL was passed in via a filename or in an ESEL,
53     // use that.  Otherwise, create one.
54     auto rawPelPath = ad.getValue(additional_data::rawPEL);
55     if (rawPelPath)
56     {
57         addRawPEL(*rawPelPath, obmcLogID);
58     }
59     else
60     {
61         auto esel = ad.getValue(additional_data::esel);
62         if (esel)
63         {
64             addESELPEL(*esel, obmcLogID);
65         }
66         else
67         {
68             createPEL(message, obmcLogID, timestamp, severity, additionalData,
69                       associations);
70         }
71     }
72 }
73 
74 void Manager::addRawPEL(const std::string& rawPelPath, uint32_t obmcLogID)
75 {
76     if (fs::exists(rawPelPath))
77     {
78         std::ifstream file(rawPelPath, std::ios::in | std::ios::binary);
79 
80         auto data = std::vector<uint8_t>(std::istreambuf_iterator<char>(file),
81                                          std::istreambuf_iterator<char>());
82         if (file.fail())
83         {
84             log<level::ERR>("Filesystem error reading a raw PEL",
85                             entry("PELFILE=%s", rawPelPath.c_str()),
86                             entry("OBMCLOGID=%d", obmcLogID));
87             // TODO, Decide what to do here. Maybe nothing.
88             return;
89         }
90 
91         file.close();
92 
93         addPEL(data, obmcLogID);
94     }
95     else
96     {
97         log<level::ERR>("Raw PEL file from BMC event log does not exist",
98                         entry("PELFILE=%s", (rawPelPath).c_str()),
99                         entry("OBMCLOGID=%d", obmcLogID));
100     }
101 }
102 
103 void Manager::addPEL(std::vector<uint8_t>& pelData, uint32_t obmcLogID)
104 {
105 
106     auto pel = std::make_unique<openpower::pels::PEL>(pelData, obmcLogID);
107     if (pel->valid())
108     {
109         // PELs created by others still need these fields set by us.
110         pel->assignID();
111         pel->setCommitTime();
112 
113         try
114         {
115             log<level::DEBUG>("Adding external PEL to repo",
116                               entry("PEL_ID=0x%X", pel->id()));
117 
118             _repo.add(pel);
119         }
120         catch (std::exception& e)
121         {
122             // Probably a full or r/o filesystem, not much we can do.
123             log<level::ERR>("Unable to add PEL to Repository",
124                             entry("PEL_ID=0x%X", pel->id()));
125         }
126     }
127     else
128     {
129         log<level::ERR>("Invalid PEL received from the host",
130                         entry("OBMCLOGID=%d", obmcLogID));
131 
132         AdditionalData ad;
133         ad.add("PLID", getNumberString("0x%08X", pel->plid()));
134         ad.add("OBMC_LOG_ID", std::to_string(obmcLogID));
135         ad.add("PEL_SIZE", std::to_string(pelData.size()));
136 
137         std::string asciiString;
138         auto src = pel->primarySRC();
139         if (src)
140         {
141             asciiString = (*src)->asciiString();
142         }
143 
144         ad.add("SRC", asciiString);
145 
146         _eventLogger.log("org.open_power.Logging.Error.BadHostPEL",
147                          Entry::Level::Error, ad);
148     }
149 }
150 
151 void Manager::addESELPEL(const std::string& esel, uint32_t obmcLogID)
152 {
153     std::vector<uint8_t> data;
154 
155     log<level::DEBUG>("Adding PEL from ESEL",
156                       entry("OBMC_LOG_ID=%d", obmcLogID));
157 
158     try
159     {
160         data = std::move(eselToRawData(esel));
161     }
162     catch (std::exception& e)
163     {
164         // Try to add it below anyway, so it follows the usual bad data path.
165         log<level::ERR>("Problems converting ESEL string to a byte vector");
166     }
167 
168     addPEL(data, obmcLogID);
169 }
170 
171 std::vector<uint8_t> Manager::eselToRawData(const std::string& esel)
172 {
173     std::vector<uint8_t> data;
174     std::string byteString;
175 
176     // As the eSEL string looks like: "50 48 00 ab ..." there are 3
177     // characters per raw byte, and since the actual PEL data starts
178     // at the 16th byte, the code will grab the PEL data starting at
179     // offset 48 in the string.
180     static constexpr size_t pelStart = 16 * 3;
181 
182     if (esel.size() <= pelStart)
183     {
184         log<level::ERR>("ESEL data too short",
185                         entry("ESEL_SIZE=%d", esel.size()));
186 
187         throw std::length_error("ESEL data too short");
188     }
189 
190     for (size_t i = pelStart; i < esel.size(); i += 3)
191     {
192         if (i + 1 < esel.size())
193         {
194             byteString = esel.substr(i, 2);
195             data.push_back(std::stoi(byteString, nullptr, 16));
196         }
197         else
198         {
199             log<level::ERR>("ESEL data too short",
200                             entry("ESEL_SIZE=%d", esel.size()));
201             throw std::length_error("ESEL data too short");
202         }
203     }
204 
205     return data;
206 }
207 
208 void Manager::erase(uint32_t obmcLogID)
209 {
210     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
211 
212     _repo.remove(id);
213 }
214 
215 bool Manager::isDeleteProhibited(uint32_t obmcLogID)
216 {
217     return false;
218 }
219 
220 void Manager::createPEL(const std::string& message, uint32_t obmcLogID,
221                         uint64_t timestamp,
222                         phosphor::logging::Entry::Level severity,
223                         const std::vector<std::string>& additionalData,
224                         const std::vector<std::string>& associations)
225 {
226     auto entry = _registry.lookup(message, rg::LookupType::name);
227     std::string msg;
228 
229     if (entry)
230     {
231         AdditionalData ad{additionalData};
232 
233         auto pel = std::make_unique<openpower::pels::PEL>(
234             *entry, obmcLogID, timestamp, severity, ad, *_dataIface);
235 
236         _repo.add(pel);
237 
238         auto src = pel->primarySRC();
239         if (src)
240         {
241             using namespace std::literals::string_literals;
242             auto id = getNumberString("0x%08X", pel->id());
243             msg = "Created PEL "s + id + " with SRC "s + (*src)->asciiString();
244             while (msg.back() == ' ')
245             {
246                 msg.pop_back();
247             }
248             log<level::INFO>(msg.c_str());
249         }
250     }
251     else
252     {
253         // TODO ibm-openbmc/dev/1151: Create a new PEL for this case.
254         // For now, just trace it.
255         msg = "Event not found in PEL message registry: " + message;
256         log<level::INFO>(msg.c_str());
257     }
258 }
259 
260 sdbusplus::message::unix_fd Manager::getPEL(uint32_t pelID)
261 {
262     Repository::LogID id{Repository::LogID::Pel(pelID)};
263     std::optional<int> fd;
264 
265     log<level::DEBUG>("getPEL", entry("PEL_ID=0x%X", pelID));
266 
267     try
268     {
269         fd = _repo.getPELFD(id);
270     }
271     catch (std::exception& e)
272     {
273         throw common_error::InternalFailure();
274     }
275 
276     if (!fd)
277     {
278         throw common_error::InvalidArgument();
279     }
280 
281     scheduleFDClose(*fd);
282 
283     return *fd;
284 }
285 
286 void Manager::scheduleFDClose(int fd)
287 {
288     _fdCloserEventSource = std::make_unique<sdeventplus::source::Defer>(
289         _logManager.getBus().get_event(),
290         std::bind(std::mem_fn(&Manager::closeFD), this, fd,
291                   std::placeholders::_1));
292 }
293 
294 void Manager::closeFD(int fd, sdeventplus::source::EventBase& source)
295 {
296     close(fd);
297     _fdCloserEventSource.reset();
298 }
299 
300 std::vector<uint8_t> Manager::getPELFromOBMCID(uint32_t obmcLogID)
301 {
302     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
303     std::optional<std::vector<uint8_t>> data;
304 
305     log<level::DEBUG>("getPELFromOBMCID", entry("OBMC_LOG_ID=%d", obmcLogID));
306 
307     try
308     {
309         data = _repo.getPELData(id);
310     }
311     catch (std::exception& e)
312     {
313         throw common_error::InternalFailure();
314     }
315 
316     if (!data)
317     {
318         throw common_error::InvalidArgument();
319     }
320 
321     return *data;
322 }
323 
324 void Manager::hostAck(uint32_t pelID)
325 {
326     Repository::LogID id{Repository::LogID::Pel(pelID)};
327 
328     log<level::DEBUG>("HostAck", entry("PEL_ID=0x%X", pelID));
329 
330     if (!_repo.hasPEL(id))
331     {
332         throw common_error::InvalidArgument();
333     }
334 
335     if (_hostNotifier)
336     {
337         _hostNotifier->ackPEL(pelID);
338     }
339 }
340 
341 void Manager::hostReject(uint32_t pelID, RejectionReason reason)
342 {
343     Repository::LogID id{Repository::LogID::Pel(pelID)};
344 
345     log<level::DEBUG>("HostReject", entry("PEL_ID=0x%X", pelID),
346                       entry("REASON=%d", static_cast<int>(reason)));
347 
348     if (!_repo.hasPEL(id))
349     {
350         throw common_error::InvalidArgument();
351     }
352 
353     if (reason == RejectionReason::BadPEL)
354     {
355         AdditionalData data;
356         data.add("BAD_ID", getNumberString("0x%08X", pelID));
357         _eventLogger.log("org.open_power.Logging.Error.SentBadPELToHost",
358                          Entry::Level::Informational, data);
359         if (_hostNotifier)
360         {
361             _hostNotifier->setBadPEL(pelID);
362         }
363     }
364     else if ((reason == RejectionReason::HostFull) && _hostNotifier)
365     {
366         _hostNotifier->setHostFull(pelID);
367     }
368 }
369 
370 } // namespace pels
371 } // namespace openpower
372