blob: 19aeffd80f0c05dae159be110665dd2c3cd75929 [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 Spinler22421b92020-07-17 09:41:08 -050022#include <fmt/format.h>
Matt Spinlerff9cec22020-07-15 13:06:35 -050023#include <sys/inotify.h>
Matt Spinler6b1a5c82020-01-07 08:48:53 -060024#include <unistd.h>
25
Matt Spinler89fa0822019-07-17 13:54:30 -050026#include <filesystem>
27#include <fstream>
Matt Spinlera34ab722019-12-16 10:39:32 -060028#include <xyz/openbmc_project/Common/error.hpp>
Matt Spinler56ad2a02020-03-26 14:00:52 -050029#include <xyz/openbmc_project/Logging/Create/server.hpp>
Matt Spinler4e8078c2019-07-09 13:22:32 -050030
31namespace openpower
32{
33namespace pels
34{
35
36using namespace phosphor::logging;
Matt Spinler89fa0822019-07-17 13:54:30 -050037namespace fs = std::filesystem;
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +080038namespace rg = openpower::pels::message;
Matt Spinler4e8078c2019-07-09 13:22:32 -050039
Matt Spinlera34ab722019-12-16 10:39:32 -060040namespace common_error = sdbusplus::xyz::openbmc_project::Common::Error;
41
Matt Spinler56ad2a02020-03-26 14:00:52 -050042using Create = sdbusplus::xyz::openbmc_project::Logging::server::Create;
43
Matt Spinler4e8078c2019-07-09 13:22:32 -050044namespace additional_data
45{
46constexpr auto rawPEL = "RAWPEL";
Matt Spinler19e72902020-01-24 11:05:20 -060047constexpr auto esel = "ESEL";
Matt Spinler30ddc9f2020-07-16 15:39:59 -050048constexpr auto error = "ERROR_NAME";
Matt Spinler19e72902020-01-24 11:05:20 -060049} // namespace additional_data
Matt Spinler4e8078c2019-07-09 13:22:32 -050050
Matt Spinler30ddc9f2020-07-16 15:39:59 -050051constexpr auto defaultLogMessage = "xyz.openbmc_project.Logging.Error.Default";
52
Matt Spinlerff9cec22020-07-15 13:06:35 -050053Manager::~Manager()
54{
55 if (_pelFileDeleteFD != -1)
56 {
57 if (_pelFileDeleteWatchFD != -1)
58 {
59 inotify_rm_watch(_pelFileDeleteFD, _pelFileDeleteWatchFD);
60 }
61 close(_pelFileDeleteFD);
62 }
63}
64
Matt Spinler4e8078c2019-07-09 13:22:32 -050065void Manager::create(const std::string& message, uint32_t obmcLogID,
66 uint64_t timestamp, Entry::Level severity,
67 const std::vector<std::string>& additionalData,
Matt Spinler56ad2a02020-03-26 14:00:52 -050068 const std::vector<std::string>& associations,
69 const FFDCEntries& ffdc)
Matt Spinler4e8078c2019-07-09 13:22:32 -050070{
71 AdditionalData ad{additionalData};
72
Matt Spinler19e72902020-01-24 11:05:20 -060073 // If a PEL was passed in via a filename or in an ESEL,
74 // use that. Otherwise, create one.
Matt Spinler4e8078c2019-07-09 13:22:32 -050075 auto rawPelPath = ad.getValue(additional_data::rawPEL);
76 if (rawPelPath)
77 {
78 addRawPEL(*rawPelPath, obmcLogID);
79 }
80 else
81 {
Matt Spinler19e72902020-01-24 11:05:20 -060082 auto esel = ad.getValue(additional_data::esel);
83 if (esel)
84 {
85 addESELPEL(*esel, obmcLogID);
86 }
87 else
88 {
89 createPEL(message, obmcLogID, timestamp, severity, additionalData,
Matt Spinler56ad2a02020-03-26 14:00:52 -050090 associations, ffdc);
Matt Spinler19e72902020-01-24 11:05:20 -060091 }
Matt Spinler4e8078c2019-07-09 13:22:32 -050092 }
93}
94
95void Manager::addRawPEL(const std::string& rawPelPath, uint32_t obmcLogID)
96{
Matt Spinler89fa0822019-07-17 13:54:30 -050097 if (fs::exists(rawPelPath))
98 {
99 std::ifstream file(rawPelPath, std::ios::in | std::ios::binary);
100
101 auto data = std::vector<uint8_t>(std::istreambuf_iterator<char>(file),
102 std::istreambuf_iterator<char>());
103 if (file.fail())
104 {
105 log<level::ERR>("Filesystem error reading a raw PEL",
106 entry("PELFILE=%s", rawPelPath.c_str()),
107 entry("OBMCLOGID=%d", obmcLogID));
108 // TODO, Decide what to do here. Maybe nothing.
109 return;
110 }
111
112 file.close();
113
Matt Spinler19e72902020-01-24 11:05:20 -0600114 addPEL(data, obmcLogID);
Matt Spinler89fa0822019-07-17 13:54:30 -0500115 }
116 else
117 {
118 log<level::ERR>("Raw PEL file from BMC event log does not exist",
119 entry("PELFILE=%s", (rawPelPath).c_str()),
120 entry("OBMCLOGID=%d", obmcLogID));
121 }
Matt Spinler4e8078c2019-07-09 13:22:32 -0500122}
123
Matt Spinler19e72902020-01-24 11:05:20 -0600124void Manager::addPEL(std::vector<uint8_t>& pelData, uint32_t obmcLogID)
125{
126
127 auto pel = std::make_unique<openpower::pels::PEL>(pelData, obmcLogID);
128 if (pel->valid())
129 {
130 // PELs created by others still need these fields set by us.
131 pel->assignID();
132 pel->setCommitTime();
133
134 try
135 {
Matt Spinler6321ba32020-07-17 09:58:19 -0500136 log<level::INFO>(
137 fmt::format("Adding external PEL {:#x} (BMC ID {}) to repo",
138 pel->id(), obmcLogID)
139 .c_str());
Matt Spinler5f5352e2020-03-05 16:23:27 -0600140
Matt Spinler19e72902020-01-24 11:05:20 -0600141 _repo.add(pel);
Matt Spinler7e727a32020-07-07 15:00:17 -0500142
143 if (_repo.sizeWarning())
144 {
145 scheduleRepoPrune();
146 }
Matt Spinler19e72902020-01-24 11:05:20 -0600147 }
148 catch (std::exception& e)
149 {
150 // Probably a full or r/o filesystem, not much we can do.
151 log<level::ERR>("Unable to add PEL to Repository",
152 entry("PEL_ID=0x%X", pel->id()));
153 }
154 }
155 else
156 {
157 log<level::ERR>("Invalid PEL received from the host",
158 entry("OBMCLOGID=%d", obmcLogID));
159
160 AdditionalData ad;
161 ad.add("PLID", getNumberString("0x%08X", pel->plid()));
162 ad.add("OBMC_LOG_ID", std::to_string(obmcLogID));
163 ad.add("PEL_SIZE", std::to_string(pelData.size()));
164
165 std::string asciiString;
166 auto src = pel->primarySRC();
167 if (src)
168 {
169 asciiString = (*src)->asciiString();
170 }
171
172 ad.add("SRC", asciiString);
173
174 _eventLogger.log("org.open_power.Logging.Error.BadHostPEL",
175 Entry::Level::Error, ad);
Matt Spinlerfe721892020-04-02 10:28:08 -0500176
177 // Save it to a file for debug in the lab. Just keep the latest.
178 // Not adding it to the PEL because it could already be max size
179 // and don't want to truncate an already invalid PEL.
180 std::ofstream pelFile{getPELRepoPath() / "badPEL"};
181 pelFile.write(reinterpret_cast<const char*>(pelData.data()),
182 pelData.size());
Matt Spinler19e72902020-01-24 11:05:20 -0600183 }
184}
185
186void Manager::addESELPEL(const std::string& esel, uint32_t obmcLogID)
187{
188 std::vector<uint8_t> data;
189
Matt Spinler5f5352e2020-03-05 16:23:27 -0600190 log<level::DEBUG>("Adding PEL from ESEL",
191 entry("OBMC_LOG_ID=%d", obmcLogID));
192
Matt Spinler19e72902020-01-24 11:05:20 -0600193 try
194 {
195 data = std::move(eselToRawData(esel));
196 }
197 catch (std::exception& e)
198 {
199 // Try to add it below anyway, so it follows the usual bad data path.
200 log<level::ERR>("Problems converting ESEL string to a byte vector");
201 }
202
203 addPEL(data, obmcLogID);
204}
205
206std::vector<uint8_t> Manager::eselToRawData(const std::string& esel)
207{
208 std::vector<uint8_t> data;
209 std::string byteString;
210
211 // As the eSEL string looks like: "50 48 00 ab ..." there are 3
212 // characters per raw byte, and since the actual PEL data starts
213 // at the 16th byte, the code will grab the PEL data starting at
214 // offset 48 in the string.
215 static constexpr size_t pelStart = 16 * 3;
216
217 if (esel.size() <= pelStart)
218 {
219 log<level::ERR>("ESEL data too short",
220 entry("ESEL_SIZE=%d", esel.size()));
221
222 throw std::length_error("ESEL data too short");
223 }
224
225 for (size_t i = pelStart; i < esel.size(); i += 3)
226 {
227 if (i + 1 < esel.size())
228 {
229 byteString = esel.substr(i, 2);
230 data.push_back(std::stoi(byteString, nullptr, 16));
231 }
232 else
233 {
234 log<level::ERR>("ESEL data too short",
235 entry("ESEL_SIZE=%d", esel.size()));
236 throw std::length_error("ESEL data too short");
237 }
238 }
239
240 return data;
241}
242
Matt Spinler4e8078c2019-07-09 13:22:32 -0500243void Manager::erase(uint32_t obmcLogID)
244{
Matt Spinler475e5742019-07-18 16:09:49 -0500245 Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
246
247 _repo.remove(id);
Matt Spinler4e8078c2019-07-09 13:22:32 -0500248}
249
250bool Manager::isDeleteProhibited(uint32_t obmcLogID)
251{
252 return false;
253}
254
Matt Spinler56ad2a02020-03-26 14:00:52 -0500255PelFFDC Manager::convertToPelFFDC(const FFDCEntries& ffdc)
256{
257 PelFFDC pelFFDC;
258
259 std::for_each(ffdc.begin(), ffdc.end(), [&pelFFDC](const auto& f) {
260 PelFFDCfile pf;
261 pf.subType = std::get<ffdcSubtypePos>(f);
262 pf.version = std::get<ffdcVersionPos>(f);
263 pf.fd = std::get<ffdcFDPos>(f);
264
265 switch (std::get<ffdcFormatPos>(f))
266 {
267 case Create::FFDCFormat::JSON:
268 pf.format = UserDataFormat::json;
269 break;
270 case Create::FFDCFormat::CBOR:
271 pf.format = UserDataFormat::cbor;
272 break;
273 case Create::FFDCFormat::Text:
274 pf.format = UserDataFormat::text;
275 break;
276 case Create::FFDCFormat::Custom:
277 pf.format = UserDataFormat::custom;
278 break;
279 }
280
281 pelFFDC.push_back(pf);
282 });
283
284 return pelFFDC;
285}
286
Matt Spinler4e8078c2019-07-09 13:22:32 -0500287void Manager::createPEL(const std::string& message, uint32_t obmcLogID,
288 uint64_t timestamp,
289 phosphor::logging::Entry::Level severity,
290 const std::vector<std::string>& additionalData,
Matt Spinler56ad2a02020-03-26 14:00:52 -0500291 const std::vector<std::string>& associations,
292 const FFDCEntries& ffdc)
Matt Spinler4e8078c2019-07-09 13:22:32 -0500293{
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800294 auto entry = _registry.lookup(message, rg::LookupType::name);
Matt Spinler30ddc9f2020-07-16 15:39:59 -0500295 auto pelFFDC = convertToPelFFDC(ffdc);
296 AdditionalData ad{additionalData};
Matt Spinler1d4c74a2019-12-16 14:40:21 -0600297 std::string msg;
Matt Spinler67456c22019-10-21 12:22:49 -0500298
Matt Spinler30ddc9f2020-07-16 15:39:59 -0500299 if (!entry)
Matt Spinler67456c22019-10-21 12:22:49 -0500300 {
Matt Spinler30ddc9f2020-07-16 15:39:59 -0500301 // Instead, get the default entry that means there is no
302 // other matching entry. This error will still use the
303 // AdditionalData values of the original error, and this
304 // code will add the error message value that wasn't found
305 // to this AD. This way, there will at least be a PEL,
306 // possibly with callouts, to allow users to debug the
307 // issue that caused the error even without its own PEL.
Matt Spinler1d4c74a2019-12-16 14:40:21 -0600308 msg = "Event not found in PEL message registry: " + message;
309 log<level::INFO>(msg.c_str());
Matt Spinler30ddc9f2020-07-16 15:39:59 -0500310
311 entry = _registry.lookup(defaultLogMessage, rg::LookupType::name);
312 if (!entry)
313 {
314 log<level::ERR>("Default event not found in PEL message registry");
315 return;
316 }
317
318 ad.add(additional_data::error, message);
319 }
320
321 auto pel = std::make_unique<openpower::pels::PEL>(
322 *entry, obmcLogID, timestamp, severity, ad, pelFFDC, *_dataIface);
323
324 _repo.add(pel);
325
326 if (_repo.sizeWarning())
327 {
328 scheduleRepoPrune();
329 }
330
331 auto src = pel->primarySRC();
332 if (src)
333 {
Matt Spinler22421b92020-07-17 09:41:08 -0500334 auto msg =
335 fmt::format("Created PEL {:#x} (BMC ID {}) with SRC {}", pel->id(),
336 pel->obmcLogID(), (*src)->asciiString());
Matt Spinler30ddc9f2020-07-16 15:39:59 -0500337 while (msg.back() == ' ')
338 {
339 msg.pop_back();
340 }
341 log<level::INFO>(msg.c_str());
Matt Spinler1d4c74a2019-12-16 14:40:21 -0600342 }
Matt Spinler4e8078c2019-07-09 13:22:32 -0500343}
344
Matt Spinlera34ab722019-12-16 10:39:32 -0600345sdbusplus::message::unix_fd Manager::getPEL(uint32_t pelID)
346{
347 Repository::LogID id{Repository::LogID::Pel(pelID)};
348 std::optional<int> fd;
349
Matt Spinler5f5352e2020-03-05 16:23:27 -0600350 log<level::DEBUG>("getPEL", entry("PEL_ID=0x%X", pelID));
351
Matt Spinlera34ab722019-12-16 10:39:32 -0600352 try
353 {
354 fd = _repo.getPELFD(id);
355 }
356 catch (std::exception& e)
357 {
358 throw common_error::InternalFailure();
359 }
360
361 if (!fd)
362 {
363 throw common_error::InvalidArgument();
364 }
365
Matt Spinler6b1a5c82020-01-07 08:48:53 -0600366 scheduleFDClose(*fd);
367
Matt Spinlera34ab722019-12-16 10:39:32 -0600368 return *fd;
369}
370
Matt Spinler6b1a5c82020-01-07 08:48:53 -0600371void Manager::scheduleFDClose(int fd)
372{
373 _fdCloserEventSource = std::make_unique<sdeventplus::source::Defer>(
Matt Spinlerff9cec22020-07-15 13:06:35 -0500374 _event, std::bind(std::mem_fn(&Manager::closeFD), this, fd,
375 std::placeholders::_1));
Matt Spinler6b1a5c82020-01-07 08:48:53 -0600376}
377
378void Manager::closeFD(int fd, sdeventplus::source::EventBase& source)
379{
380 close(fd);
381 _fdCloserEventSource.reset();
382}
383
Matt Spinlera34ab722019-12-16 10:39:32 -0600384std::vector<uint8_t> Manager::getPELFromOBMCID(uint32_t obmcLogID)
385{
386 Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
387 std::optional<std::vector<uint8_t>> data;
388
Matt Spinler5f5352e2020-03-05 16:23:27 -0600389 log<level::DEBUG>("getPELFromOBMCID", entry("OBMC_LOG_ID=%d", obmcLogID));
390
Matt Spinlera34ab722019-12-16 10:39:32 -0600391 try
392 {
393 data = _repo.getPELData(id);
394 }
395 catch (std::exception& e)
396 {
397 throw common_error::InternalFailure();
398 }
399
400 if (!data)
401 {
402 throw common_error::InvalidArgument();
403 }
404
405 return *data;
406}
407
408void Manager::hostAck(uint32_t pelID)
409{
410 Repository::LogID id{Repository::LogID::Pel(pelID)};
411
Matt Spinler5f5352e2020-03-05 16:23:27 -0600412 log<level::DEBUG>("HostAck", entry("PEL_ID=0x%X", pelID));
413
Matt Spinlera34ab722019-12-16 10:39:32 -0600414 if (!_repo.hasPEL(id))
415 {
416 throw common_error::InvalidArgument();
417 }
418
419 if (_hostNotifier)
420 {
421 _hostNotifier->ackPEL(pelID);
422 }
423}
424
425void Manager::hostReject(uint32_t pelID, RejectionReason reason)
426{
427 Repository::LogID id{Repository::LogID::Pel(pelID)};
428
Matt Spinler5f5352e2020-03-05 16:23:27 -0600429 log<level::DEBUG>("HostReject", entry("PEL_ID=0x%X", pelID),
430 entry("REASON=%d", static_cast<int>(reason)));
431
Matt Spinlera34ab722019-12-16 10:39:32 -0600432 if (!_repo.hasPEL(id))
433 {
434 throw common_error::InvalidArgument();
435 }
436
Matt Spinler05c2c6c2019-12-18 14:02:09 -0600437 if (reason == RejectionReason::BadPEL)
Matt Spinlera34ab722019-12-16 10:39:32 -0600438 {
Matt Spinler05c2c6c2019-12-18 14:02:09 -0600439 AdditionalData data;
440 data.add("BAD_ID", getNumberString("0x%08X", pelID));
441 _eventLogger.log("org.open_power.Logging.Error.SentBadPELToHost",
442 Entry::Level::Informational, data);
443 if (_hostNotifier)
Matt Spinlera34ab722019-12-16 10:39:32 -0600444 {
445 _hostNotifier->setBadPEL(pelID);
446 }
Matt Spinler05c2c6c2019-12-18 14:02:09 -0600447 }
448 else if ((reason == RejectionReason::HostFull) && _hostNotifier)
449 {
450 _hostNotifier->setHostFull(pelID);
Matt Spinlera34ab722019-12-16 10:39:32 -0600451 }
452}
453
Matt Spinler7e727a32020-07-07 15:00:17 -0500454void Manager::scheduleRepoPrune()
455{
Matt Spinler7e727a32020-07-07 15:00:17 -0500456 _repoPrunerEventSource = std::make_unique<sdeventplus::source::Defer>(
Matt Spinlerff9cec22020-07-15 13:06:35 -0500457 _event, std::bind(std::mem_fn(&Manager::pruneRepo), this,
458 std::placeholders::_1));
Matt Spinler7e727a32020-07-07 15:00:17 -0500459}
460
461void Manager::pruneRepo(sdeventplus::source::EventBase& source)
462{
463 auto idsToDelete = _repo.prune();
464
465 // Remove the OpenBMC event logs for the PELs that were just removed.
466 std::for_each(idsToDelete.begin(), idsToDelete.end(),
467 [this](auto id) { this->_logManager.erase(id); });
468
469 _repoPrunerEventSource.reset();
470}
471
Matt Spinlerff9cec22020-07-15 13:06:35 -0500472void Manager::setupPELDeleteWatch()
473{
474 _pelFileDeleteFD = inotify_init1(IN_NONBLOCK);
475 if (-1 == _pelFileDeleteFD)
476 {
477 auto e = errno;
478 std::string msg =
479 "inotify_init1 failed with errno " + std::to_string(e);
480 log<level::ERR>(msg.c_str());
481 abort();
482 }
483
484 _pelFileDeleteWatchFD = inotify_add_watch(
485 _pelFileDeleteFD, _repo.repoPath().c_str(), IN_DELETE);
486 if (-1 == _pelFileDeleteWatchFD)
487 {
488 auto e = errno;
489 std::string msg =
490 "inotify_add_watch failed with error " + std::to_string(e);
491 log<level::ERR>(msg.c_str());
492 abort();
493 }
494
495 _pelFileDeleteEventSource = std::make_unique<sdeventplus::source::IO>(
496 _event, _pelFileDeleteFD, EPOLLIN,
497 std::bind(std::mem_fn(&Manager::pelFileDeleted), this,
498 std::placeholders::_1, std::placeholders::_2,
499 std::placeholders::_3));
500}
501
502void Manager::pelFileDeleted(sdeventplus::source::IO& io, int fd,
503 uint32_t revents)
504{
505 if (!(revents & EPOLLIN))
506 {
507 return;
508 }
509
510 // An event for 1 PEL uses 48B. When all PELs are deleted at once,
511 // as many events as there is room for can be handled in one callback.
512 // A size of 2000 will allow 41 to be processed, with additional
513 // callbacks being needed to process the remaining ones.
514 std::array<uint8_t, 2000> data;
515 auto bytesRead = read(_pelFileDeleteFD, data.data(), data.size());
516 if (bytesRead < 0)
517 {
518 auto e = errno;
519 std::string msg = "Failed reading data from inotify event, errno = " +
520 std::to_string(e);
521 log<level::ERR>(msg.c_str());
522 abort();
523 }
524
525 auto offset = 0;
526 while (offset < bytesRead)
527 {
528 auto event = reinterpret_cast<inotify_event*>(&data[offset]);
529 if (event->mask & IN_DELETE)
530 {
531 std::string filename{event->name};
532
533 // Get the PEL ID from the filename and tell the
534 // repo it's been removed, and then delete the BMC
535 // event log if it's there.
536 auto pos = filename.find_first_of('_');
537 if (pos != std::string::npos)
538 {
539 try
540 {
541 auto idString = filename.substr(pos + 1);
542 auto pelID = std::stoul(idString, nullptr, 16);
543
544 Repository::LogID id{Repository::LogID::Pel(pelID)};
545 auto removedLogID = _repo.remove(id);
546 if (removedLogID)
547 {
548 _logManager.erase(removedLogID->obmcID.id);
549 }
550 }
551 catch (const std::exception& e)
552 {
553 log<level::INFO>("Could not find PEL ID from its filename",
554 entry("FILENAME=%s", filename.c_str()));
555 }
556 }
557 }
558
559 offset += offsetof(inotify_event, name) + event->len;
560 }
561}
Matt Spinler4e8078c2019-07-09 13:22:32 -0500562} // namespace pels
563} // namespace openpower