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 #include "service_indicators.hpp" 22 23 #include <fmt/format.h> 24 #include <sys/inotify.h> 25 #include <unistd.h> 26 27 #include <filesystem> 28 #include <fstream> 29 #include <xyz/openbmc_project/Common/error.hpp> 30 #include <xyz/openbmc_project/Logging/Create/server.hpp> 31 32 namespace openpower 33 { 34 namespace pels 35 { 36 37 using namespace phosphor::logging; 38 namespace fs = std::filesystem; 39 namespace rg = openpower::pels::message; 40 41 namespace common_error = sdbusplus::xyz::openbmc_project::Common::Error; 42 43 using Create = sdbusplus::xyz::openbmc_project::Logging::server::Create; 44 45 namespace additional_data 46 { 47 constexpr auto rawPEL = "RAWPEL"; 48 constexpr auto esel = "ESEL"; 49 constexpr auto error = "ERROR_NAME"; 50 } // namespace additional_data 51 52 constexpr auto defaultLogMessage = "xyz.openbmc_project.Logging.Error.Default"; 53 54 Manager::~Manager() 55 { 56 if (_pelFileDeleteFD != -1) 57 { 58 if (_pelFileDeleteWatchFD != -1) 59 { 60 inotify_rm_watch(_pelFileDeleteFD, _pelFileDeleteWatchFD); 61 } 62 close(_pelFileDeleteFD); 63 } 64 } 65 66 void Manager::create(const std::string& message, uint32_t obmcLogID, 67 uint64_t timestamp, Entry::Level severity, 68 const std::vector<std::string>& additionalData, 69 const std::vector<std::string>& associations, 70 const FFDCEntries& ffdc) 71 { 72 AdditionalData ad{additionalData}; 73 74 // If a PEL was passed in via a filename or in an ESEL, 75 // use that. Otherwise, create one. 76 auto rawPelPath = ad.getValue(additional_data::rawPEL); 77 if (rawPelPath) 78 { 79 addRawPEL(*rawPelPath, obmcLogID); 80 } 81 else 82 { 83 auto esel = ad.getValue(additional_data::esel); 84 if (esel) 85 { 86 addESELPEL(*esel, obmcLogID); 87 } 88 else 89 { 90 createPEL(message, obmcLogID, timestamp, severity, additionalData, 91 associations, ffdc); 92 } 93 } 94 95 setEntryPath(obmcLogID); 96 } 97 98 void Manager::addRawPEL(const std::string& rawPelPath, uint32_t obmcLogID) 99 { 100 if (fs::exists(rawPelPath)) 101 { 102 std::ifstream file(rawPelPath, std::ios::in | std::ios::binary); 103 104 auto data = std::vector<uint8_t>(std::istreambuf_iterator<char>(file), 105 std::istreambuf_iterator<char>()); 106 if (file.fail()) 107 { 108 log<level::ERR>("Filesystem error reading a raw PEL", 109 entry("PELFILE=%s", rawPelPath.c_str()), 110 entry("OBMCLOGID=%d", obmcLogID)); 111 // TODO, Decide what to do here. Maybe nothing. 112 return; 113 } 114 115 file.close(); 116 117 addPEL(data, obmcLogID); 118 } 119 else 120 { 121 log<level::ERR>("Raw PEL file from BMC event log does not exist", 122 entry("PELFILE=%s", (rawPelPath).c_str()), 123 entry("OBMCLOGID=%d", obmcLogID)); 124 } 125 } 126 127 void Manager::addPEL(std::vector<uint8_t>& pelData, uint32_t obmcLogID) 128 { 129 130 auto pel = std::make_unique<openpower::pels::PEL>(pelData, obmcLogID); 131 if (pel->valid()) 132 { 133 // PELs created by others still need these fields set by us. 134 pel->assignID(); 135 pel->setCommitTime(); 136 137 try 138 { 139 log<level::DEBUG>( 140 fmt::format("Adding external PEL {:#x} (BMC ID {}) to repo", 141 pel->id(), obmcLogID) 142 .c_str()); 143 144 _repo.add(pel); 145 146 if (_repo.sizeWarning()) 147 { 148 scheduleRepoPrune(); 149 } 150 151 // Activate any resulting service indicators if necessary 152 auto policy = service_indicators::getPolicy(*_dataIface); 153 policy->activate(*pel); 154 } 155 catch (std::exception& e) 156 { 157 // Probably a full or r/o filesystem, not much we can do. 158 log<level::ERR>("Unable to add PEL to Repository", 159 entry("PEL_ID=0x%X", pel->id())); 160 } 161 162 // Check if firmware should quiesce system due to error 163 checkPelAndQuiesce(pel); 164 } 165 else 166 { 167 log<level::ERR>("Invalid PEL received from the host", 168 entry("OBMCLOGID=%d", obmcLogID)); 169 170 AdditionalData ad; 171 ad.add("PLID", getNumberString("0x%08X", pel->plid())); 172 ad.add("OBMC_LOG_ID", std::to_string(obmcLogID)); 173 ad.add("PEL_SIZE", std::to_string(pelData.size())); 174 175 std::string asciiString; 176 auto src = pel->primarySRC(); 177 if (src) 178 { 179 asciiString = (*src)->asciiString(); 180 } 181 182 ad.add("SRC", asciiString); 183 184 _eventLogger.log("org.open_power.Logging.Error.BadHostPEL", 185 Entry::Level::Error, ad); 186 187 // Save it to a file for debug in the lab. Just keep the latest. 188 // Not adding it to the PEL because it could already be max size 189 // and don't want to truncate an already invalid PEL. 190 std::ofstream pelFile{getPELRepoPath() / "badPEL"}; 191 pelFile.write(reinterpret_cast<const char*>(pelData.data()), 192 pelData.size()); 193 } 194 } 195 196 void Manager::addESELPEL(const std::string& esel, uint32_t obmcLogID) 197 { 198 std::vector<uint8_t> data; 199 200 log<level::DEBUG>("Adding PEL from ESEL", 201 entry("OBMC_LOG_ID=%d", obmcLogID)); 202 203 try 204 { 205 data = std::move(eselToRawData(esel)); 206 } 207 catch (std::exception& e) 208 { 209 // Try to add it below anyway, so it follows the usual bad data path. 210 log<level::ERR>("Problems converting ESEL string to a byte vector"); 211 } 212 213 addPEL(data, obmcLogID); 214 } 215 216 std::vector<uint8_t> Manager::eselToRawData(const std::string& esel) 217 { 218 std::vector<uint8_t> data; 219 std::string byteString; 220 221 // As the eSEL string looks like: "50 48 00 ab ..." there are 3 222 // characters per raw byte, and since the actual PEL data starts 223 // at the 16th byte, the code will grab the PEL data starting at 224 // offset 48 in the string. 225 static constexpr size_t pelStart = 16 * 3; 226 227 if (esel.size() <= pelStart) 228 { 229 log<level::ERR>("ESEL data too short", 230 entry("ESEL_SIZE=%d", esel.size())); 231 232 throw std::length_error("ESEL data too short"); 233 } 234 235 for (size_t i = pelStart; i < esel.size(); i += 3) 236 { 237 if (i + 1 < esel.size()) 238 { 239 byteString = esel.substr(i, 2); 240 data.push_back(std::stoi(byteString, nullptr, 16)); 241 } 242 else 243 { 244 log<level::ERR>("ESEL data too short", 245 entry("ESEL_SIZE=%d", esel.size())); 246 throw std::length_error("ESEL data too short"); 247 } 248 } 249 250 return data; 251 } 252 253 void Manager::erase(uint32_t obmcLogID) 254 { 255 Repository::LogID id{Repository::LogID::Obmc(obmcLogID)}; 256 257 _repo.remove(id); 258 } 259 260 bool Manager::isDeleteProhibited(uint32_t obmcLogID) 261 { 262 return false; 263 } 264 265 PelFFDC Manager::convertToPelFFDC(const FFDCEntries& ffdc) 266 { 267 PelFFDC pelFFDC; 268 269 std::for_each(ffdc.begin(), ffdc.end(), [&pelFFDC](const auto& f) { 270 PelFFDCfile pf; 271 pf.subType = std::get<ffdcSubtypePos>(f); 272 pf.version = std::get<ffdcVersionPos>(f); 273 pf.fd = std::get<ffdcFDPos>(f); 274 275 switch (std::get<ffdcFormatPos>(f)) 276 { 277 case Create::FFDCFormat::JSON: 278 pf.format = UserDataFormat::json; 279 break; 280 case Create::FFDCFormat::CBOR: 281 pf.format = UserDataFormat::cbor; 282 break; 283 case Create::FFDCFormat::Text: 284 pf.format = UserDataFormat::text; 285 break; 286 case Create::FFDCFormat::Custom: 287 pf.format = UserDataFormat::custom; 288 break; 289 } 290 291 pelFFDC.push_back(pf); 292 }); 293 294 return pelFFDC; 295 } 296 297 void Manager::createPEL(const std::string& message, uint32_t obmcLogID, 298 uint64_t timestamp, 299 phosphor::logging::Entry::Level severity, 300 const std::vector<std::string>& additionalData, 301 const std::vector<std::string>& associations, 302 const FFDCEntries& ffdc) 303 { 304 auto entry = _registry.lookup(message, rg::LookupType::name); 305 auto pelFFDC = convertToPelFFDC(ffdc); 306 AdditionalData ad{additionalData}; 307 std::string msg; 308 309 if (!entry) 310 { 311 // Instead, get the default entry that means there is no 312 // other matching entry. This error will still use the 313 // AdditionalData values of the original error, and this 314 // code will add the error message value that wasn't found 315 // to this AD. This way, there will at least be a PEL, 316 // possibly with callouts, to allow users to debug the 317 // issue that caused the error even without its own PEL. 318 msg = "Event not found in PEL message registry: " + message; 319 log<level::INFO>(msg.c_str()); 320 321 entry = _registry.lookup(defaultLogMessage, rg::LookupType::name); 322 if (!entry) 323 { 324 log<level::ERR>("Default event not found in PEL message registry"); 325 return; 326 } 327 328 ad.add(additional_data::error, message); 329 } 330 331 auto pel = std::make_unique<openpower::pels::PEL>( 332 *entry, obmcLogID, timestamp, severity, ad, pelFFDC, *_dataIface); 333 334 _repo.add(pel); 335 336 if (_repo.sizeWarning()) 337 { 338 scheduleRepoPrune(); 339 } 340 341 auto src = pel->primarySRC(); 342 if (src) 343 { 344 auto msg = 345 fmt::format("Created PEL {:#x} (BMC ID {}) with SRC {}", pel->id(), 346 pel->obmcLogID(), (*src)->asciiString()); 347 while (msg.back() == ' ') 348 { 349 msg.pop_back(); 350 } 351 log<level::INFO>(msg.c_str()); 352 } 353 354 // Activate any resulting service indicators if necessary 355 auto policy = service_indicators::getPolicy(*_dataIface); 356 policy->activate(*pel); 357 358 // Check if firmware should quiesce system due to error 359 checkPelAndQuiesce(pel); 360 } 361 362 sdbusplus::message::unix_fd Manager::getPEL(uint32_t pelID) 363 { 364 Repository::LogID id{Repository::LogID::Pel(pelID)}; 365 std::optional<int> fd; 366 367 log<level::DEBUG>("getPEL", entry("PEL_ID=0x%X", pelID)); 368 369 try 370 { 371 fd = _repo.getPELFD(id); 372 } 373 catch (std::exception& e) 374 { 375 throw common_error::InternalFailure(); 376 } 377 378 if (!fd) 379 { 380 throw common_error::InvalidArgument(); 381 } 382 383 scheduleFDClose(*fd); 384 385 return *fd; 386 } 387 388 void Manager::scheduleFDClose(int fd) 389 { 390 _fdCloserEventSource = std::make_unique<sdeventplus::source::Defer>( 391 _event, std::bind(std::mem_fn(&Manager::closeFD), this, fd, 392 std::placeholders::_1)); 393 } 394 395 void Manager::closeFD(int fd, sdeventplus::source::EventBase& source) 396 { 397 close(fd); 398 _fdCloserEventSource.reset(); 399 } 400 401 std::vector<uint8_t> Manager::getPELFromOBMCID(uint32_t obmcLogID) 402 { 403 Repository::LogID id{Repository::LogID::Obmc(obmcLogID)}; 404 std::optional<std::vector<uint8_t>> data; 405 406 log<level::DEBUG>("getPELFromOBMCID", entry("OBMC_LOG_ID=%d", obmcLogID)); 407 408 try 409 { 410 data = _repo.getPELData(id); 411 } 412 catch (std::exception& e) 413 { 414 throw common_error::InternalFailure(); 415 } 416 417 if (!data) 418 { 419 throw common_error::InvalidArgument(); 420 } 421 422 return *data; 423 } 424 425 void Manager::hostAck(uint32_t pelID) 426 { 427 Repository::LogID id{Repository::LogID::Pel(pelID)}; 428 429 log<level::DEBUG>("HostAck", entry("PEL_ID=0x%X", pelID)); 430 431 if (!_repo.hasPEL(id)) 432 { 433 throw common_error::InvalidArgument(); 434 } 435 436 if (_hostNotifier) 437 { 438 _hostNotifier->ackPEL(pelID); 439 } 440 } 441 442 void Manager::hostReject(uint32_t pelID, RejectionReason reason) 443 { 444 Repository::LogID id{Repository::LogID::Pel(pelID)}; 445 446 log<level::DEBUG>("HostReject", entry("PEL_ID=0x%X", pelID), 447 entry("REASON=%d", static_cast<int>(reason))); 448 449 if (!_repo.hasPEL(id)) 450 { 451 throw common_error::InvalidArgument(); 452 } 453 454 if (reason == RejectionReason::BadPEL) 455 { 456 AdditionalData data; 457 data.add("BAD_ID", getNumberString("0x%08X", pelID)); 458 _eventLogger.log("org.open_power.Logging.Error.SentBadPELToHost", 459 Entry::Level::Informational, data); 460 if (_hostNotifier) 461 { 462 _hostNotifier->setBadPEL(pelID); 463 } 464 } 465 else if ((reason == RejectionReason::HostFull) && _hostNotifier) 466 { 467 _hostNotifier->setHostFull(pelID); 468 } 469 } 470 471 void Manager::scheduleRepoPrune() 472 { 473 _repoPrunerEventSource = std::make_unique<sdeventplus::source::Defer>( 474 _event, std::bind(std::mem_fn(&Manager::pruneRepo), this, 475 std::placeholders::_1)); 476 } 477 478 void Manager::pruneRepo(sdeventplus::source::EventBase& source) 479 { 480 auto idsToDelete = _repo.prune(); 481 482 // Remove the OpenBMC event logs for the PELs that were just removed. 483 std::for_each(idsToDelete.begin(), idsToDelete.end(), 484 [this](auto id) { this->_logManager.erase(id); }); 485 486 _repoPrunerEventSource.reset(); 487 } 488 489 void Manager::setupPELDeleteWatch() 490 { 491 _pelFileDeleteFD = inotify_init1(IN_NONBLOCK); 492 if (-1 == _pelFileDeleteFD) 493 { 494 auto e = errno; 495 std::string msg = 496 "inotify_init1 failed with errno " + std::to_string(e); 497 log<level::ERR>(msg.c_str()); 498 abort(); 499 } 500 501 _pelFileDeleteWatchFD = inotify_add_watch( 502 _pelFileDeleteFD, _repo.repoPath().c_str(), IN_DELETE); 503 if (-1 == _pelFileDeleteWatchFD) 504 { 505 auto e = errno; 506 std::string msg = 507 "inotify_add_watch failed with error " + std::to_string(e); 508 log<level::ERR>(msg.c_str()); 509 abort(); 510 } 511 512 _pelFileDeleteEventSource = std::make_unique<sdeventplus::source::IO>( 513 _event, _pelFileDeleteFD, EPOLLIN, 514 std::bind(std::mem_fn(&Manager::pelFileDeleted), this, 515 std::placeholders::_1, std::placeholders::_2, 516 std::placeholders::_3)); 517 } 518 519 void Manager::pelFileDeleted(sdeventplus::source::IO& io, int fd, 520 uint32_t revents) 521 { 522 if (!(revents & EPOLLIN)) 523 { 524 return; 525 } 526 527 // An event for 1 PEL uses 48B. When all PELs are deleted at once, 528 // as many events as there is room for can be handled in one callback. 529 // A size of 2000 will allow 41 to be processed, with additional 530 // callbacks being needed to process the remaining ones. 531 std::array<uint8_t, 2000> data; 532 auto bytesRead = read(_pelFileDeleteFD, data.data(), data.size()); 533 if (bytesRead < 0) 534 { 535 auto e = errno; 536 std::string msg = "Failed reading data from inotify event, errno = " + 537 std::to_string(e); 538 log<level::ERR>(msg.c_str()); 539 abort(); 540 } 541 542 auto offset = 0; 543 while (offset < bytesRead) 544 { 545 auto event = reinterpret_cast<inotify_event*>(&data[offset]); 546 if (event->mask & IN_DELETE) 547 { 548 std::string filename{event->name}; 549 550 // Get the PEL ID from the filename and tell the 551 // repo it's been removed, and then delete the BMC 552 // event log if it's there. 553 auto pos = filename.find_first_of('_'); 554 if (pos != std::string::npos) 555 { 556 try 557 { 558 auto idString = filename.substr(pos + 1); 559 auto pelID = std::stoul(idString, nullptr, 16); 560 561 Repository::LogID id{Repository::LogID::Pel(pelID)}; 562 auto removedLogID = _repo.remove(id); 563 if (removedLogID) 564 { 565 _logManager.erase(removedLogID->obmcID.id); 566 } 567 } 568 catch (const std::exception& e) 569 { 570 log<level::INFO>("Could not find PEL ID from its filename", 571 entry("FILENAME=%s", filename.c_str())); 572 } 573 } 574 } 575 576 offset += offsetof(inotify_event, name) + event->len; 577 } 578 } 579 580 std::tuple<uint32_t, uint32_t> Manager::createPELWithFFDCFiles( 581 std::string message, Entry::Level severity, 582 std::map<std::string, std::string> additionalData, 583 std::vector<std::tuple< 584 sdbusplus::xyz::openbmc_project::Logging::server::Create::FFDCFormat, 585 uint8_t, uint8_t, sdbusplus::message::unix_fd>> 586 fFDC) 587 { 588 _logManager.createWithFFDC(message, severity, additionalData, fFDC); 589 590 return {_logManager.lastEntryID(), _repo.lastPelID()}; 591 } 592 593 void Manager::checkPelAndQuiesce(std::unique_ptr<openpower::pels::PEL>& pel) 594 { 595 if (pel->userHeader().severity() == 596 static_cast<uint8_t>(SeverityType::nonError)) 597 { 598 log<level::DEBUG>("PEL severity informational. no quiesce needed"); 599 return; 600 } 601 if (!_logManager.isQuiesceOnErrorEnabled()) 602 { 603 log<level::DEBUG>("QuiesceOnHwError not enabled, no quiesce needed"); 604 return; 605 } 606 607 // Now check if it has any type of callout 608 if (pel->isCalloutPresent()) 609 { 610 log<level::INFO>("QuiesceOnHwError enabled, PEL severity not nonError, " 611 "and callout is present"); 612 613 _logManager.quiesceOnError(pel->obmcLogID()); 614 } 615 } 616 617 void Manager::setEntryPath(uint32_t obmcLogID) 618 { 619 Repository::LogID id{Repository::LogID::Obmc(obmcLogID)}; 620 if (auto attributes = _repo.getPELAttributes(id); attributes) 621 { 622 auto& attr = attributes.value().get(); 623 auto entry = _logManager.entries.find(obmcLogID); 624 if (entry != _logManager.entries.end()) 625 { 626 entry->second->path(attr.path); 627 } 628 } 629 } 630 631 } // namespace pels 632 } // namespace openpower 633