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