blob: c8b5872125608a7f6afa85fe872110d837a28b09 [file] [log] [blame]
Matt Spinler711d51d2019-11-06 09:36:51 -06001/**
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 */
Matt Spinler4e8078c2019-07-09 13:22:32 -050016#include "manager.hpp"
17
18#include "additional_data.hpp"
Matt Spinler05c2c6c2019-12-18 14:02:09 -060019#include "json_utils.hpp"
Matt Spinler89fa0822019-07-17 13:54:30 -050020#include "pel.hpp"
21
Matt Spinlerff9cec22020-07-15 13:06:35 -050022#include <sys/inotify.h>
Matt Spinler6b1a5c82020-01-07 08:48:53 -060023#include <unistd.h>
24
Matt Spinler89fa0822019-07-17 13:54:30 -050025#include <filesystem>
26#include <fstream>
Matt Spinlera34ab722019-12-16 10:39:32 -060027#include <xyz/openbmc_project/Common/error.hpp>
Matt Spinler56ad2a02020-03-26 14:00:52 -050028#include <xyz/openbmc_project/Logging/Create/server.hpp>
Matt Spinler4e8078c2019-07-09 13:22:32 -050029
30namespace openpower
31{
32namespace pels
33{
34
35using namespace phosphor::logging;
Matt Spinler89fa0822019-07-17 13:54:30 -050036namespace fs = std::filesystem;
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +080037namespace rg = openpower::pels::message;
Matt Spinler4e8078c2019-07-09 13:22:32 -050038
Matt Spinlera34ab722019-12-16 10:39:32 -060039namespace common_error = sdbusplus::xyz::openbmc_project::Common::Error;
40
Matt Spinler56ad2a02020-03-26 14:00:52 -050041using Create = sdbusplus::xyz::openbmc_project::Logging::server::Create;
42
Matt Spinler4e8078c2019-07-09 13:22:32 -050043namespace additional_data
44{
45constexpr auto rawPEL = "RAWPEL";
Matt Spinler19e72902020-01-24 11:05:20 -060046constexpr auto esel = "ESEL";
Matt Spinler30ddc9f2020-07-16 15:39:59 -050047constexpr auto error = "ERROR_NAME";
Matt Spinler19e72902020-01-24 11:05:20 -060048} // namespace additional_data
Matt Spinler4e8078c2019-07-09 13:22:32 -050049
Matt Spinler30ddc9f2020-07-16 15:39:59 -050050constexpr auto defaultLogMessage = "xyz.openbmc_project.Logging.Error.Default";
51
Matt Spinlerff9cec22020-07-15 13:06:35 -050052Manager::~Manager()
53{
54 if (_pelFileDeleteFD != -1)
55 {
56 if (_pelFileDeleteWatchFD != -1)
57 {
58 inotify_rm_watch(_pelFileDeleteFD, _pelFileDeleteWatchFD);
59 }
60 close(_pelFileDeleteFD);
61 }
62}
63
Matt Spinler4e8078c2019-07-09 13:22:32 -050064void Manager::create(const std::string& message, uint32_t obmcLogID,
65 uint64_t timestamp, Entry::Level severity,
66 const std::vector<std::string>& additionalData,
Matt Spinler56ad2a02020-03-26 14:00:52 -050067 const std::vector<std::string>& associations,
68 const FFDCEntries& ffdc)
Matt Spinler4e8078c2019-07-09 13:22:32 -050069{
70 AdditionalData ad{additionalData};
71
Matt Spinler19e72902020-01-24 11:05:20 -060072 // If a PEL was passed in via a filename or in an ESEL,
73 // use that. Otherwise, create one.
Matt Spinler4e8078c2019-07-09 13:22:32 -050074 auto rawPelPath = ad.getValue(additional_data::rawPEL);
75 if (rawPelPath)
76 {
77 addRawPEL(*rawPelPath, obmcLogID);
78 }
79 else
80 {
Matt Spinler19e72902020-01-24 11:05:20 -060081 auto esel = ad.getValue(additional_data::esel);
82 if (esel)
83 {
84 addESELPEL(*esel, obmcLogID);
85 }
86 else
87 {
88 createPEL(message, obmcLogID, timestamp, severity, additionalData,
Matt Spinler56ad2a02020-03-26 14:00:52 -050089 associations, ffdc);
Matt Spinler19e72902020-01-24 11:05:20 -060090 }
Matt Spinler4e8078c2019-07-09 13:22:32 -050091 }
92}
93
94void Manager::addRawPEL(const std::string& rawPelPath, uint32_t obmcLogID)
95{
Matt Spinler89fa0822019-07-17 13:54:30 -050096 if (fs::exists(rawPelPath))
97 {
98 std::ifstream file(rawPelPath, std::ios::in | std::ios::binary);
99
100 auto data = std::vector<uint8_t>(std::istreambuf_iterator<char>(file),
101 std::istreambuf_iterator<char>());
102 if (file.fail())
103 {
104 log<level::ERR>("Filesystem error reading a raw PEL",
105 entry("PELFILE=%s", rawPelPath.c_str()),
106 entry("OBMCLOGID=%d", obmcLogID));
107 // TODO, Decide what to do here. Maybe nothing.
108 return;
109 }
110
111 file.close();
112
Matt Spinler19e72902020-01-24 11:05:20 -0600113 addPEL(data, obmcLogID);
Matt Spinler89fa0822019-07-17 13:54:30 -0500114 }
115 else
116 {
117 log<level::ERR>("Raw PEL file from BMC event log does not exist",
118 entry("PELFILE=%s", (rawPelPath).c_str()),
119 entry("OBMCLOGID=%d", obmcLogID));
120 }
Matt Spinler4e8078c2019-07-09 13:22:32 -0500121}
122
Matt Spinler19e72902020-01-24 11:05:20 -0600123void Manager::addPEL(std::vector<uint8_t>& pelData, uint32_t obmcLogID)
124{
125
126 auto pel = std::make_unique<openpower::pels::PEL>(pelData, obmcLogID);
127 if (pel->valid())
128 {
129 // PELs created by others still need these fields set by us.
130 pel->assignID();
131 pel->setCommitTime();
132
133 try
134 {
Matt Spinler5f5352e2020-03-05 16:23:27 -0600135 log<level::DEBUG>("Adding external PEL to repo",
136 entry("PEL_ID=0x%X", pel->id()));
137
Matt Spinler19e72902020-01-24 11:05:20 -0600138 _repo.add(pel);
Matt Spinler7e727a32020-07-07 15:00:17 -0500139
140 if (_repo.sizeWarning())
141 {
142 scheduleRepoPrune();
143 }
Matt Spinler19e72902020-01-24 11:05:20 -0600144 }
145 catch (std::exception& e)
146 {
147 // Probably a full or r/o filesystem, not much we can do.
148 log<level::ERR>("Unable to add PEL to Repository",
149 entry("PEL_ID=0x%X", pel->id()));
150 }
151 }
152 else
153 {
154 log<level::ERR>("Invalid PEL received from the host",
155 entry("OBMCLOGID=%d", obmcLogID));
156
157 AdditionalData ad;
158 ad.add("PLID", getNumberString("0x%08X", pel->plid()));
159 ad.add("OBMC_LOG_ID", std::to_string(obmcLogID));
160 ad.add("PEL_SIZE", std::to_string(pelData.size()));
161
162 std::string asciiString;
163 auto src = pel->primarySRC();
164 if (src)
165 {
166 asciiString = (*src)->asciiString();
167 }
168
169 ad.add("SRC", asciiString);
170
171 _eventLogger.log("org.open_power.Logging.Error.BadHostPEL",
172 Entry::Level::Error, ad);
Matt Spinlerfe721892020-04-02 10:28:08 -0500173
174 // Save it to a file for debug in the lab. Just keep the latest.
175 // Not adding it to the PEL because it could already be max size
176 // and don't want to truncate an already invalid PEL.
177 std::ofstream pelFile{getPELRepoPath() / "badPEL"};
178 pelFile.write(reinterpret_cast<const char*>(pelData.data()),
179 pelData.size());
Matt Spinler19e72902020-01-24 11:05:20 -0600180 }
181}
182
183void Manager::addESELPEL(const std::string& esel, uint32_t obmcLogID)
184{
185 std::vector<uint8_t> data;
186
Matt Spinler5f5352e2020-03-05 16:23:27 -0600187 log<level::DEBUG>("Adding PEL from ESEL",
188 entry("OBMC_LOG_ID=%d", obmcLogID));
189
Matt Spinler19e72902020-01-24 11:05:20 -0600190 try
191 {
192 data = std::move(eselToRawData(esel));
193 }
194 catch (std::exception& e)
195 {
196 // Try to add it below anyway, so it follows the usual bad data path.
197 log<level::ERR>("Problems converting ESEL string to a byte vector");
198 }
199
200 addPEL(data, obmcLogID);
201}
202
203std::vector<uint8_t> Manager::eselToRawData(const std::string& esel)
204{
205 std::vector<uint8_t> data;
206 std::string byteString;
207
208 // As the eSEL string looks like: "50 48 00 ab ..." there are 3
209 // characters per raw byte, and since the actual PEL data starts
210 // at the 16th byte, the code will grab the PEL data starting at
211 // offset 48 in the string.
212 static constexpr size_t pelStart = 16 * 3;
213
214 if (esel.size() <= pelStart)
215 {
216 log<level::ERR>("ESEL data too short",
217 entry("ESEL_SIZE=%d", esel.size()));
218
219 throw std::length_error("ESEL data too short");
220 }
221
222 for (size_t i = pelStart; i < esel.size(); i += 3)
223 {
224 if (i + 1 < esel.size())
225 {
226 byteString = esel.substr(i, 2);
227 data.push_back(std::stoi(byteString, nullptr, 16));
228 }
229 else
230 {
231 log<level::ERR>("ESEL data too short",
232 entry("ESEL_SIZE=%d", esel.size()));
233 throw std::length_error("ESEL data too short");
234 }
235 }
236
237 return data;
238}
239
Matt Spinler4e8078c2019-07-09 13:22:32 -0500240void Manager::erase(uint32_t obmcLogID)
241{
Matt Spinler475e5742019-07-18 16:09:49 -0500242 Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
243
244 _repo.remove(id);
Matt Spinler4e8078c2019-07-09 13:22:32 -0500245}
246
247bool Manager::isDeleteProhibited(uint32_t obmcLogID)
248{
249 return false;
250}
251
Matt Spinler56ad2a02020-03-26 14:00:52 -0500252PelFFDC Manager::convertToPelFFDC(const FFDCEntries& ffdc)
253{
254 PelFFDC pelFFDC;
255
256 std::for_each(ffdc.begin(), ffdc.end(), [&pelFFDC](const auto& f) {
257 PelFFDCfile pf;
258 pf.subType = std::get<ffdcSubtypePos>(f);
259 pf.version = std::get<ffdcVersionPos>(f);
260 pf.fd = std::get<ffdcFDPos>(f);
261
262 switch (std::get<ffdcFormatPos>(f))
263 {
264 case Create::FFDCFormat::JSON:
265 pf.format = UserDataFormat::json;
266 break;
267 case Create::FFDCFormat::CBOR:
268 pf.format = UserDataFormat::cbor;
269 break;
270 case Create::FFDCFormat::Text:
271 pf.format = UserDataFormat::text;
272 break;
273 case Create::FFDCFormat::Custom:
274 pf.format = UserDataFormat::custom;
275 break;
276 }
277
278 pelFFDC.push_back(pf);
279 });
280
281 return pelFFDC;
282}
283
Matt Spinler4e8078c2019-07-09 13:22:32 -0500284void Manager::createPEL(const std::string& message, uint32_t obmcLogID,
285 uint64_t timestamp,
286 phosphor::logging::Entry::Level severity,
287 const std::vector<std::string>& additionalData,
Matt Spinler56ad2a02020-03-26 14:00:52 -0500288 const std::vector<std::string>& associations,
289 const FFDCEntries& ffdc)
Matt Spinler4e8078c2019-07-09 13:22:32 -0500290{
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800291 auto entry = _registry.lookup(message, rg::LookupType::name);
Matt Spinler30ddc9f2020-07-16 15:39:59 -0500292 auto pelFFDC = convertToPelFFDC(ffdc);
293 AdditionalData ad{additionalData};
Matt Spinler1d4c74a2019-12-16 14:40:21 -0600294 std::string msg;
Matt Spinler67456c22019-10-21 12:22:49 -0500295
Matt Spinler30ddc9f2020-07-16 15:39:59 -0500296 if (!entry)
Matt Spinler67456c22019-10-21 12:22:49 -0500297 {
Matt Spinler30ddc9f2020-07-16 15:39:59 -0500298 // Instead, get the default entry that means there is no
299 // other matching entry. This error will still use the
300 // AdditionalData values of the original error, and this
301 // code will add the error message value that wasn't found
302 // to this AD. This way, there will at least be a PEL,
303 // possibly with callouts, to allow users to debug the
304 // issue that caused the error even without its own PEL.
Matt Spinler1d4c74a2019-12-16 14:40:21 -0600305 msg = "Event not found in PEL message registry: " + message;
306 log<level::INFO>(msg.c_str());
Matt Spinler30ddc9f2020-07-16 15:39:59 -0500307
308 entry = _registry.lookup(defaultLogMessage, rg::LookupType::name);
309 if (!entry)
310 {
311 log<level::ERR>("Default event not found in PEL message registry");
312 return;
313 }
314
315 ad.add(additional_data::error, message);
316 }
317
318 auto pel = std::make_unique<openpower::pels::PEL>(
319 *entry, obmcLogID, timestamp, severity, ad, pelFFDC, *_dataIface);
320
321 _repo.add(pel);
322
323 if (_repo.sizeWarning())
324 {
325 scheduleRepoPrune();
326 }
327
328 auto src = pel->primarySRC();
329 if (src)
330 {
331 using namespace std::literals::string_literals;
332 auto id = getNumberString("0x%08X", pel->id());
333 msg = "Created PEL "s + id + " with SRC "s + (*src)->asciiString();
334 while (msg.back() == ' ')
335 {
336 msg.pop_back();
337 }
338 log<level::INFO>(msg.c_str());
Matt Spinler1d4c74a2019-12-16 14:40:21 -0600339 }
Matt Spinler4e8078c2019-07-09 13:22:32 -0500340}
341
Matt Spinlera34ab722019-12-16 10:39:32 -0600342sdbusplus::message::unix_fd Manager::getPEL(uint32_t pelID)
343{
344 Repository::LogID id{Repository::LogID::Pel(pelID)};
345 std::optional<int> fd;
346
Matt Spinler5f5352e2020-03-05 16:23:27 -0600347 log<level::DEBUG>("getPEL", entry("PEL_ID=0x%X", pelID));
348
Matt Spinlera34ab722019-12-16 10:39:32 -0600349 try
350 {
351 fd = _repo.getPELFD(id);
352 }
353 catch (std::exception& e)
354 {
355 throw common_error::InternalFailure();
356 }
357
358 if (!fd)
359 {
360 throw common_error::InvalidArgument();
361 }
362
Matt Spinler6b1a5c82020-01-07 08:48:53 -0600363 scheduleFDClose(*fd);
364
Matt Spinlera34ab722019-12-16 10:39:32 -0600365 return *fd;
366}
367
Matt Spinler6b1a5c82020-01-07 08:48:53 -0600368void Manager::scheduleFDClose(int fd)
369{
370 _fdCloserEventSource = std::make_unique<sdeventplus::source::Defer>(
Matt Spinlerff9cec22020-07-15 13:06:35 -0500371 _event, std::bind(std::mem_fn(&Manager::closeFD), this, fd,
372 std::placeholders::_1));
Matt Spinler6b1a5c82020-01-07 08:48:53 -0600373}
374
375void Manager::closeFD(int fd, sdeventplus::source::EventBase& source)
376{
377 close(fd);
378 _fdCloserEventSource.reset();
379}
380
Matt Spinlera34ab722019-12-16 10:39:32 -0600381std::vector<uint8_t> Manager::getPELFromOBMCID(uint32_t obmcLogID)
382{
383 Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
384 std::optional<std::vector<uint8_t>> data;
385
Matt Spinler5f5352e2020-03-05 16:23:27 -0600386 log<level::DEBUG>("getPELFromOBMCID", entry("OBMC_LOG_ID=%d", obmcLogID));
387
Matt Spinlera34ab722019-12-16 10:39:32 -0600388 try
389 {
390 data = _repo.getPELData(id);
391 }
392 catch (std::exception& e)
393 {
394 throw common_error::InternalFailure();
395 }
396
397 if (!data)
398 {
399 throw common_error::InvalidArgument();
400 }
401
402 return *data;
403}
404
405void Manager::hostAck(uint32_t pelID)
406{
407 Repository::LogID id{Repository::LogID::Pel(pelID)};
408
Matt Spinler5f5352e2020-03-05 16:23:27 -0600409 log<level::DEBUG>("HostAck", entry("PEL_ID=0x%X", pelID));
410
Matt Spinlera34ab722019-12-16 10:39:32 -0600411 if (!_repo.hasPEL(id))
412 {
413 throw common_error::InvalidArgument();
414 }
415
416 if (_hostNotifier)
417 {
418 _hostNotifier->ackPEL(pelID);
419 }
420}
421
422void Manager::hostReject(uint32_t pelID, RejectionReason reason)
423{
424 Repository::LogID id{Repository::LogID::Pel(pelID)};
425
Matt Spinler5f5352e2020-03-05 16:23:27 -0600426 log<level::DEBUG>("HostReject", entry("PEL_ID=0x%X", pelID),
427 entry("REASON=%d", static_cast<int>(reason)));
428
Matt Spinlera34ab722019-12-16 10:39:32 -0600429 if (!_repo.hasPEL(id))
430 {
431 throw common_error::InvalidArgument();
432 }
433
Matt Spinler05c2c6c2019-12-18 14:02:09 -0600434 if (reason == RejectionReason::BadPEL)
Matt Spinlera34ab722019-12-16 10:39:32 -0600435 {
Matt Spinler05c2c6c2019-12-18 14:02:09 -0600436 AdditionalData data;
437 data.add("BAD_ID", getNumberString("0x%08X", pelID));
438 _eventLogger.log("org.open_power.Logging.Error.SentBadPELToHost",
439 Entry::Level::Informational, data);
440 if (_hostNotifier)
Matt Spinlera34ab722019-12-16 10:39:32 -0600441 {
442 _hostNotifier->setBadPEL(pelID);
443 }
Matt Spinler05c2c6c2019-12-18 14:02:09 -0600444 }
445 else if ((reason == RejectionReason::HostFull) && _hostNotifier)
446 {
447 _hostNotifier->setHostFull(pelID);
Matt Spinlera34ab722019-12-16 10:39:32 -0600448 }
449}
450
Matt Spinler7e727a32020-07-07 15:00:17 -0500451void Manager::scheduleRepoPrune()
452{
Matt Spinler7e727a32020-07-07 15:00:17 -0500453 _repoPrunerEventSource = std::make_unique<sdeventplus::source::Defer>(
Matt Spinlerff9cec22020-07-15 13:06:35 -0500454 _event, std::bind(std::mem_fn(&Manager::pruneRepo), this,
455 std::placeholders::_1));
Matt Spinler7e727a32020-07-07 15:00:17 -0500456}
457
458void Manager::pruneRepo(sdeventplus::source::EventBase& source)
459{
460 auto idsToDelete = _repo.prune();
461
462 // Remove the OpenBMC event logs for the PELs that were just removed.
463 std::for_each(idsToDelete.begin(), idsToDelete.end(),
464 [this](auto id) { this->_logManager.erase(id); });
465
466 _repoPrunerEventSource.reset();
467}
468
Matt Spinlerff9cec22020-07-15 13:06:35 -0500469void Manager::setupPELDeleteWatch()
470{
471 _pelFileDeleteFD = inotify_init1(IN_NONBLOCK);
472 if (-1 == _pelFileDeleteFD)
473 {
474 auto e = errno;
475 std::string msg =
476 "inotify_init1 failed with errno " + std::to_string(e);
477 log<level::ERR>(msg.c_str());
478 abort();
479 }
480
481 _pelFileDeleteWatchFD = inotify_add_watch(
482 _pelFileDeleteFD, _repo.repoPath().c_str(), IN_DELETE);
483 if (-1 == _pelFileDeleteWatchFD)
484 {
485 auto e = errno;
486 std::string msg =
487 "inotify_add_watch failed with error " + std::to_string(e);
488 log<level::ERR>(msg.c_str());
489 abort();
490 }
491
492 _pelFileDeleteEventSource = std::make_unique<sdeventplus::source::IO>(
493 _event, _pelFileDeleteFD, EPOLLIN,
494 std::bind(std::mem_fn(&Manager::pelFileDeleted), this,
495 std::placeholders::_1, std::placeholders::_2,
496 std::placeholders::_3));
497}
498
499void Manager::pelFileDeleted(sdeventplus::source::IO& io, int fd,
500 uint32_t revents)
501{
502 if (!(revents & EPOLLIN))
503 {
504 return;
505 }
506
507 // An event for 1 PEL uses 48B. When all PELs are deleted at once,
508 // as many events as there is room for can be handled in one callback.
509 // A size of 2000 will allow 41 to be processed, with additional
510 // callbacks being needed to process the remaining ones.
511 std::array<uint8_t, 2000> data;
512 auto bytesRead = read(_pelFileDeleteFD, data.data(), data.size());
513 if (bytesRead < 0)
514 {
515 auto e = errno;
516 std::string msg = "Failed reading data from inotify event, errno = " +
517 std::to_string(e);
518 log<level::ERR>(msg.c_str());
519 abort();
520 }
521
522 auto offset = 0;
523 while (offset < bytesRead)
524 {
525 auto event = reinterpret_cast<inotify_event*>(&data[offset]);
526 if (event->mask & IN_DELETE)
527 {
528 std::string filename{event->name};
529
530 // Get the PEL ID from the filename and tell the
531 // repo it's been removed, and then delete the BMC
532 // event log if it's there.
533 auto pos = filename.find_first_of('_');
534 if (pos != std::string::npos)
535 {
536 try
537 {
538 auto idString = filename.substr(pos + 1);
539 auto pelID = std::stoul(idString, nullptr, 16);
540
541 Repository::LogID id{Repository::LogID::Pel(pelID)};
542 auto removedLogID = _repo.remove(id);
543 if (removedLogID)
544 {
545 _logManager.erase(removedLogID->obmcID.id);
546 }
547 }
548 catch (const std::exception& e)
549 {
550 log<level::INFO>("Could not find PEL ID from its filename",
551 entry("FILENAME=%s", filename.c_str()));
552 }
553 }
554 }
555
556 offset += offsetof(inotify_event, name) + event->len;
557 }
558}
Matt Spinler4e8078c2019-07-09 13:22:32 -0500559} // namespace pels
560} // namespace openpower