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 #include <xyz/openbmc_project/Logging/Create/server.hpp>
28 
29 namespace openpower
30 {
31 namespace pels
32 {
33 
34 using namespace phosphor::logging;
35 namespace fs = std::filesystem;
36 namespace rg = openpower::pels::message;
37 
38 namespace common_error = sdbusplus::xyz::openbmc_project::Common::Error;
39 
40 using Create = sdbusplus::xyz::openbmc_project::Logging::server::Create;
41 
42 namespace additional_data
43 {
44 constexpr auto rawPEL = "RAWPEL";
45 constexpr auto esel = "ESEL";
46 } // namespace additional_data
47 
48 void Manager::create(const std::string& message, uint32_t obmcLogID,
49                      uint64_t timestamp, Entry::Level severity,
50                      const std::vector<std::string>& additionalData,
51                      const std::vector<std::string>& associations,
52                      const FFDCEntries& ffdc)
53 {
54     AdditionalData ad{additionalData};
55 
56     // If a PEL was passed in via a filename or in an ESEL,
57     // use that.  Otherwise, create one.
58     auto rawPelPath = ad.getValue(additional_data::rawPEL);
59     if (rawPelPath)
60     {
61         addRawPEL(*rawPelPath, obmcLogID);
62     }
63     else
64     {
65         auto esel = ad.getValue(additional_data::esel);
66         if (esel)
67         {
68             addESELPEL(*esel, obmcLogID);
69         }
70         else
71         {
72             createPEL(message, obmcLogID, timestamp, severity, additionalData,
73                       associations, ffdc);
74         }
75     }
76 }
77 
78 void Manager::addRawPEL(const std::string& rawPelPath, uint32_t obmcLogID)
79 {
80     if (fs::exists(rawPelPath))
81     {
82         std::ifstream file(rawPelPath, std::ios::in | std::ios::binary);
83 
84         auto data = std::vector<uint8_t>(std::istreambuf_iterator<char>(file),
85                                          std::istreambuf_iterator<char>());
86         if (file.fail())
87         {
88             log<level::ERR>("Filesystem error reading a raw PEL",
89                             entry("PELFILE=%s", rawPelPath.c_str()),
90                             entry("OBMCLOGID=%d", obmcLogID));
91             // TODO, Decide what to do here. Maybe nothing.
92             return;
93         }
94 
95         file.close();
96 
97         addPEL(data, obmcLogID);
98     }
99     else
100     {
101         log<level::ERR>("Raw PEL file from BMC event log does not exist",
102                         entry("PELFILE=%s", (rawPelPath).c_str()),
103                         entry("OBMCLOGID=%d", obmcLogID));
104     }
105 }
106 
107 void Manager::addPEL(std::vector<uint8_t>& pelData, uint32_t obmcLogID)
108 {
109 
110     auto pel = std::make_unique<openpower::pels::PEL>(pelData, obmcLogID);
111     if (pel->valid())
112     {
113         // PELs created by others still need these fields set by us.
114         pel->assignID();
115         pel->setCommitTime();
116 
117         try
118         {
119             log<level::DEBUG>("Adding external PEL to repo",
120                               entry("PEL_ID=0x%X", pel->id()));
121 
122             _repo.add(pel);
123         }
124         catch (std::exception& e)
125         {
126             // Probably a full or r/o filesystem, not much we can do.
127             log<level::ERR>("Unable to add PEL to Repository",
128                             entry("PEL_ID=0x%X", pel->id()));
129         }
130     }
131     else
132     {
133         log<level::ERR>("Invalid PEL received from the host",
134                         entry("OBMCLOGID=%d", obmcLogID));
135 
136         AdditionalData ad;
137         ad.add("PLID", getNumberString("0x%08X", pel->plid()));
138         ad.add("OBMC_LOG_ID", std::to_string(obmcLogID));
139         ad.add("PEL_SIZE", std::to_string(pelData.size()));
140 
141         std::string asciiString;
142         auto src = pel->primarySRC();
143         if (src)
144         {
145             asciiString = (*src)->asciiString();
146         }
147 
148         ad.add("SRC", asciiString);
149 
150         _eventLogger.log("org.open_power.Logging.Error.BadHostPEL",
151                          Entry::Level::Error, ad);
152 
153         // Save it to a file for debug in the lab.  Just keep the latest.
154         // Not adding it to the PEL because it could already be max size
155         // and don't want to truncate an already invalid PEL.
156         std::ofstream pelFile{getPELRepoPath() / "badPEL"};
157         pelFile.write(reinterpret_cast<const char*>(pelData.data()),
158                       pelData.size());
159     }
160 }
161 
162 void Manager::addESELPEL(const std::string& esel, uint32_t obmcLogID)
163 {
164     std::vector<uint8_t> data;
165 
166     log<level::DEBUG>("Adding PEL from ESEL",
167                       entry("OBMC_LOG_ID=%d", obmcLogID));
168 
169     try
170     {
171         data = std::move(eselToRawData(esel));
172     }
173     catch (std::exception& e)
174     {
175         // Try to add it below anyway, so it follows the usual bad data path.
176         log<level::ERR>("Problems converting ESEL string to a byte vector");
177     }
178 
179     addPEL(data, obmcLogID);
180 }
181 
182 std::vector<uint8_t> Manager::eselToRawData(const std::string& esel)
183 {
184     std::vector<uint8_t> data;
185     std::string byteString;
186 
187     // As the eSEL string looks like: "50 48 00 ab ..." there are 3
188     // characters per raw byte, and since the actual PEL data starts
189     // at the 16th byte, the code will grab the PEL data starting at
190     // offset 48 in the string.
191     static constexpr size_t pelStart = 16 * 3;
192 
193     if (esel.size() <= pelStart)
194     {
195         log<level::ERR>("ESEL data too short",
196                         entry("ESEL_SIZE=%d", esel.size()));
197 
198         throw std::length_error("ESEL data too short");
199     }
200 
201     for (size_t i = pelStart; i < esel.size(); i += 3)
202     {
203         if (i + 1 < esel.size())
204         {
205             byteString = esel.substr(i, 2);
206             data.push_back(std::stoi(byteString, nullptr, 16));
207         }
208         else
209         {
210             log<level::ERR>("ESEL data too short",
211                             entry("ESEL_SIZE=%d", esel.size()));
212             throw std::length_error("ESEL data too short");
213         }
214     }
215 
216     return data;
217 }
218 
219 void Manager::erase(uint32_t obmcLogID)
220 {
221     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
222 
223     _repo.remove(id);
224 }
225 
226 bool Manager::isDeleteProhibited(uint32_t obmcLogID)
227 {
228     return false;
229 }
230 
231 PelFFDC Manager::convertToPelFFDC(const FFDCEntries& ffdc)
232 {
233     PelFFDC pelFFDC;
234 
235     std::for_each(ffdc.begin(), ffdc.end(), [&pelFFDC](const auto& f) {
236         PelFFDCfile pf;
237         pf.subType = std::get<ffdcSubtypePos>(f);
238         pf.version = std::get<ffdcVersionPos>(f);
239         pf.fd = std::get<ffdcFDPos>(f);
240 
241         switch (std::get<ffdcFormatPos>(f))
242         {
243             case Create::FFDCFormat::JSON:
244                 pf.format = UserDataFormat::json;
245                 break;
246             case Create::FFDCFormat::CBOR:
247                 pf.format = UserDataFormat::cbor;
248                 break;
249             case Create::FFDCFormat::Text:
250                 pf.format = UserDataFormat::text;
251                 break;
252             case Create::FFDCFormat::Custom:
253                 pf.format = UserDataFormat::custom;
254                 break;
255         }
256 
257         pelFFDC.push_back(pf);
258     });
259 
260     return pelFFDC;
261 }
262 
263 void Manager::createPEL(const std::string& message, uint32_t obmcLogID,
264                         uint64_t timestamp,
265                         phosphor::logging::Entry::Level severity,
266                         const std::vector<std::string>& additionalData,
267                         const std::vector<std::string>& associations,
268                         const FFDCEntries& ffdc)
269 {
270     auto entry = _registry.lookup(message, rg::LookupType::name);
271     std::string msg;
272 
273     if (entry)
274     {
275         AdditionalData ad{additionalData};
276 
277         auto pelFFDC = convertToPelFFDC(ffdc);
278 
279         auto pel = std::make_unique<openpower::pels::PEL>(
280             *entry, obmcLogID, timestamp, severity, ad, pelFFDC, *_dataIface);
281 
282         _repo.add(pel);
283 
284         auto src = pel->primarySRC();
285         if (src)
286         {
287             using namespace std::literals::string_literals;
288             auto id = getNumberString("0x%08X", pel->id());
289             msg = "Created PEL "s + id + " with SRC "s + (*src)->asciiString();
290             while (msg.back() == ' ')
291             {
292                 msg.pop_back();
293             }
294             log<level::INFO>(msg.c_str());
295         }
296     }
297     else
298     {
299         // TODO ibm-openbmc/dev/1151: Create a new PEL for this case.
300         // For now, just trace it.
301         msg = "Event not found in PEL message registry: " + message;
302         log<level::INFO>(msg.c_str());
303     }
304 }
305 
306 sdbusplus::message::unix_fd Manager::getPEL(uint32_t pelID)
307 {
308     Repository::LogID id{Repository::LogID::Pel(pelID)};
309     std::optional<int> fd;
310 
311     log<level::DEBUG>("getPEL", entry("PEL_ID=0x%X", pelID));
312 
313     try
314     {
315         fd = _repo.getPELFD(id);
316     }
317     catch (std::exception& e)
318     {
319         throw common_error::InternalFailure();
320     }
321 
322     if (!fd)
323     {
324         throw common_error::InvalidArgument();
325     }
326 
327     scheduleFDClose(*fd);
328 
329     return *fd;
330 }
331 
332 void Manager::scheduleFDClose(int fd)
333 {
334     sdeventplus::Event event = sdeventplus::Event::get_default();
335 
336     _fdCloserEventSource = std::make_unique<sdeventplus::source::Defer>(
337         event, std::bind(std::mem_fn(&Manager::closeFD), this, fd,
338                          std::placeholders::_1));
339 }
340 
341 void Manager::closeFD(int fd, sdeventplus::source::EventBase& source)
342 {
343     close(fd);
344     _fdCloserEventSource.reset();
345 }
346 
347 std::vector<uint8_t> Manager::getPELFromOBMCID(uint32_t obmcLogID)
348 {
349     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
350     std::optional<std::vector<uint8_t>> data;
351 
352     log<level::DEBUG>("getPELFromOBMCID", entry("OBMC_LOG_ID=%d", obmcLogID));
353 
354     try
355     {
356         data = _repo.getPELData(id);
357     }
358     catch (std::exception& e)
359     {
360         throw common_error::InternalFailure();
361     }
362 
363     if (!data)
364     {
365         throw common_error::InvalidArgument();
366     }
367 
368     return *data;
369 }
370 
371 void Manager::hostAck(uint32_t pelID)
372 {
373     Repository::LogID id{Repository::LogID::Pel(pelID)};
374 
375     log<level::DEBUG>("HostAck", entry("PEL_ID=0x%X", pelID));
376 
377     if (!_repo.hasPEL(id))
378     {
379         throw common_error::InvalidArgument();
380     }
381 
382     if (_hostNotifier)
383     {
384         _hostNotifier->ackPEL(pelID);
385     }
386 }
387 
388 void Manager::hostReject(uint32_t pelID, RejectionReason reason)
389 {
390     Repository::LogID id{Repository::LogID::Pel(pelID)};
391 
392     log<level::DEBUG>("HostReject", entry("PEL_ID=0x%X", pelID),
393                       entry("REASON=%d", static_cast<int>(reason)));
394 
395     if (!_repo.hasPEL(id))
396     {
397         throw common_error::InvalidArgument();
398     }
399 
400     if (reason == RejectionReason::BadPEL)
401     {
402         AdditionalData data;
403         data.add("BAD_ID", getNumberString("0x%08X", pelID));
404         _eventLogger.log("org.open_power.Logging.Error.SentBadPELToHost",
405                          Entry::Level::Informational, data);
406         if (_hostNotifier)
407         {
408             _hostNotifier->setBadPEL(pelID);
409         }
410     }
411     else if ((reason == RejectionReason::HostFull) && _hostNotifier)
412     {
413         _hostNotifier->setHostFull(pelID);
414     }
415 }
416 
417 } // namespace pels
418 } // namespace openpower
419