1 #include <unistd.h> 2 3 #include <attn/attn_dbus.hpp> 4 #include <attn/attn_logging.hpp> 5 #include <attn/pel/pel_minimal.hpp> 6 #include <phosphor-logging/log.hpp> 7 8 namespace attn 9 { 10 11 /** @brief Journal entry of type INFO using phosphor logging */ 12 template <> 13 void trace<INFO>(const char* i_message) 14 { 15 phosphor::logging::log<phosphor::logging::level::INFO>(i_message); 16 } 17 18 template <> 19 void trace<ERROR>(const char* i_message) 20 { 21 phosphor::logging::log<phosphor::logging::level::ERR>(i_message); 22 } 23 24 /** @brief Tuple containing information about ffdc files */ 25 using FFDCTuple = 26 std::tuple<util::FFDCFormat, uint8_t, uint8_t, sdbusplus::message::unix_fd>; 27 28 /** @brief Gather messages from the journal */ 29 std::vector<std::string> sdjGetMessages(const std::string& field, 30 const std::string& fieldValue, 31 unsigned int max); 32 33 /** 34 * Create FFDCTuple objects corresponding to the specified FFDC files. 35 * 36 * The D-Bus method to create an error log requires a vector of tuples to 37 * pass in the FFDC file information. 38 * 39 * @param files - FFDC files 40 * @return vector of FFDCTuple objects 41 */ 42 std::vector<FFDCTuple> 43 createFFDCTuples(const std::vector<util::FFDCFile>& files) 44 { 45 std::vector<FFDCTuple> ffdcTuples{}; 46 util::transformFFDC(files, ffdcTuples); 47 48 return ffdcTuples; 49 } 50 51 /** 52 * @brief Create an FFDCFile object containing raw data 53 * 54 * Throws an exception if an error occurs. 55 * 56 * @param i_buffer - raw data to add to ffdc faw data file 57 * @param i_size - size of the raw data 58 * @return FFDCFile object 59 */ 60 util::FFDCFile createFFDCRawFile(void* i_buffer, size_t i_size) 61 { 62 util::FFDCFile file{util::FFDCFormat::Custom}; 63 64 // Write buffer to file and then reset file description file offset 65 int fd = file.getFileDescriptor(); 66 size_t numBytes = write(fd, static_cast<char*>(i_buffer), i_size); 67 if (i_size != numBytes) 68 { 69 std::stringstream traceMsg; 70 traceMsg << file.getPath().c_str() << " only " << (int)numBytes 71 << " of " << (int)i_size << " bytes written"; 72 auto strobj = traceMsg.str(); 73 trace<level::ERROR>(strobj.c_str()); 74 } 75 76 lseek(fd, 0, SEEK_SET); 77 78 return file; 79 } 80 81 /** 82 * @brief Create an FFDCFile object containing the specified lines of text data 83 * 84 * Throws an exception if an error occurs. 85 * 86 * @param lines - lines of text data to write to file 87 * @return FFDCFile object 88 */ 89 util::FFDCFile createFFDCTraceFile(const std::vector<std::string>& lines) 90 { 91 // Create FFDC file of type Text 92 util::FFDCFile file{util::FFDCFormat::Text}; 93 int fd = file.getFileDescriptor(); 94 95 // Write FFDC lines to file 96 std::string buffer; 97 for (const std::string& line : lines) 98 { 99 // Copy line to buffer. Add newline if necessary. 100 buffer = line; 101 if (line.empty() || (line.back() != '\n')) 102 { 103 buffer += '\n'; 104 } 105 106 // write buffer to file 107 size_t numBytes = write(fd, buffer.c_str(), buffer.size()); 108 if (buffer.size() != numBytes) 109 { 110 std::stringstream traceMsg; 111 traceMsg << file.getPath().c_str() << " only " << (int)numBytes 112 << " of " << (int)buffer.size() << " bytes written"; 113 auto strobj = traceMsg.str(); 114 trace<level::ERROR>(strobj.c_str()); 115 } 116 } 117 118 // Seek to beginning of file so error logging system can read data 119 lseek(fd, 0, SEEK_SET); 120 121 return file; 122 } 123 124 /** 125 * Create FDDC files from journal messages of relevant executables 126 * 127 * Parse the system journal looking for log entries created by the executables 128 * of interest for logging. For each of these entries create a ffdc trace file 129 * that will be used to create ffdc log entries. These files will be pushed 130 * onto the stack of ffdc files. 131 * 132 * @param i_files - vector of ffdc files that will become log entries 133 */ 134 void createFFDCTraceFiles(std::vector<util::FFDCFile>& i_files) 135 { 136 // Executables of interest 137 std::vector<std::string> executables{"openpower-hw-diags"}; 138 139 for (const std::string& executable : executables) 140 { 141 try 142 { 143 // get journal messages 144 std::vector<std::string> messages = 145 sdjGetMessages("SYSLOG_IDENTIFIER", executable, 30); 146 147 // Create FFDC file containing the journal messages 148 if (!messages.empty()) 149 { 150 i_files.emplace_back(createFFDCTraceFile(messages)); 151 } 152 } 153 catch (const std::exception& e) 154 { 155 trace<level::INFO>("createFFDCTraceFiles exception"); 156 std::string traceMsg = std::string(e.what(), maxTraceLen); 157 trace<level::INFO>(traceMsg.c_str()); 158 } 159 } 160 } 161 162 /** 163 * Create FFDCFile objects containing debug data to store in the error log. 164 * 165 * If an error occurs, the error is written to the journal but an exception 166 * is not thrown. 167 * 168 * @param i_buffer - raw data (if creating raw dump ffdc entry in log) 169 * @return vector of FFDCFile objects 170 */ 171 std::vector<util::FFDCFile> createFFDCFiles(char* i_buffer = nullptr, 172 size_t i_size = 0) 173 { 174 std::vector<util::FFDCFile> files{}; 175 176 // Create raw dump file 177 if ((nullptr != i_buffer) && (0 != i_size)) 178 { 179 files.emplace_back(createFFDCRawFile(i_buffer, i_size)); 180 } 181 182 // Create trace dump file 183 createFFDCTraceFiles(files); 184 185 return files; 186 } 187 188 /** 189 * Create a PEL from an existing PEL 190 * 191 * Create a new PEL based on the specified raw PEL and submit the new PEL 192 * to the backend logging code as a raw PEL. Note that additional data map 193 * here contains data to be committed to the PEL and it can also be used to 194 * create the PEL as it contains needed information. 195 * 196 * @param i_buffer - buffer containing a raw PEL 197 * @param i_additional - additional data to be added to the new PEL 198 */ 199 void createPelCustom(std::vector<uint8_t>& i_rawPel, 200 std::map<std::string, std::string> i_additional) 201 { 202 // create PEL object from buffer 203 auto tiPel = std::make_unique<pel::PelMinimal>(i_rawPel); 204 205 // The additional data contains the TI info as well as the value for the 206 // subystem that provided the TI info. Get the subystem from additional 207 // data and then populate the prmary SRC and SRC words for the custom PEL 208 // based on the sybsystem's TI info. 209 uint8_t subsystem = std::stoi(i_additional["Subsystem"]); 210 tiPel->setSubsystem(subsystem); 211 212 if (static_cast<uint8_t>(pel::SubsystemID::hypervisor) == subsystem) 213 { 214 // populate hypervisor SRC words 215 tiPel->setSrcWords(std::array<uint32_t, pel::numSrcWords>{ 216 (uint32_t)std::stoul(i_additional["0x10 SRC Word 12"], 0, 16), 217 (uint32_t)std::stoul(i_additional["0x14 SRC Word 13"], 0, 16), 218 (uint32_t)std::stoul(i_additional["0x18 SRC Word 14"], 0, 16), 219 (uint32_t)std::stoul(i_additional["0x1c SRC Word 15"], 0, 16), 220 (uint32_t)std::stoul(i_additional["0x20 SRC Word 16"], 0, 16), 221 (uint32_t)std::stoul(i_additional["0x24 SRC Word 17"], 0, 16), 222 (uint32_t)std::stoul(i_additional["0x28 SRC Word 18"], 0, 16), 223 (uint32_t)std::stoul(i_additional["0x2c SRC Word 19"], 0, 16)}); 224 225 // Populate phyp primary SRC 226 227 // char array for raw pel src 228 std::array<char, pel::asciiStringSize> srcChars{'0'}; 229 230 // src from TI info 231 std::string srcString = i_additional["SrcAscii"]; 232 233 // copy from string to char array 234 srcString.copy(srcChars.data(), 235 std::min(srcString.size(), pel::asciiStringSize), 0); 236 237 tiPel->setAsciiString(srcChars); // pel object src is char array 238 } 239 else 240 { 241 // Populate hostboot SRC words - note HB word 0 from the shared info 242 // data (additional data "0x10 HB Word") is reflected in the PEL as 243 // "reason code" so we zero it here. Also note that the first word 244 // in this group of words starts at word 0 and word 1 does not exits. 245 tiPel->setSrcWords(std::array<uint32_t, pel::numSrcWords>{ 246 (uint32_t)0x00000000, 247 (uint32_t)std::stoul(i_additional["0x14 HB Word 2"], 0, 16), 248 (uint32_t)std::stoul(i_additional["0x18 HB Word 3"], 0, 16), 249 (uint32_t)std::stoul(i_additional["0x1c HB Word 4"], 0, 16), 250 (uint32_t)std::stoul(i_additional["0x20 HB Word 5"], 0, 16), 251 (uint32_t)std::stoul(i_additional["0x24 HB Word 6"], 0, 16), 252 (uint32_t)std::stoul(i_additional["0x28 HB Word 7"], 0, 16), 253 (uint32_t)std::stoul(i_additional["0x2c HB Word 8"], 0, 16)}); 254 255 // Populate hostboot primary SRC 256 257 // char array for raw pel src 258 std::array<char, pel::asciiStringSize> srcChars{'0'}; 259 260 // src from TI info 261 std::string srcString = i_additional["SrcAscii"]; 262 263 // copy from string to char array 264 srcString.copy(srcChars.data(), 265 std::min(srcString.size(), pel::asciiStringSize), 0); 266 267 tiPel->setAsciiString(srcChars); // pel object src is char array 268 } 269 270 // set severity, event type and action flags 271 tiPel->setSeverity(static_cast<uint8_t>(pel::Severity::termination)); 272 tiPel->setType(static_cast<uint8_t>(pel::EventType::na)); 273 tiPel->setAction(static_cast<uint16_t>(pel::ActionFlags::service | 274 pel::ActionFlags::report | 275 pel::ActionFlags::call)); 276 277 // The raw PEL that we used as the basis for this custom PEL contains the 278 // attention handler trace data and does not needed to be in this PEL so 279 // we remove it here. 280 tiPel->setSectionCount(tiPel->getSectionCount() - 1); 281 282 // Update the raw PEL with the new custom PEL data 283 tiPel->raw(i_rawPel); 284 285 // create PEL from raw data 286 createPelRaw(i_rawPel); 287 } 288 289 /** 290 * Log an event handled by the attention handler 291 * 292 * Basic (non TI) events will generate a standard message-registry based PEL 293 * 294 * TI events will create two PEL's. One PEL will be informational and will 295 * contain trace information relevent to attention handler. The second PEL 296 * will be specific to the TI type (including the primary SRC) and will be 297 * based off of the TI information provided to the attention handler through 298 * shared TI info data area. 299 * 300 * @param i_event - The event type 301 * @param i_additional - Additional PEL data 302 * @param i_ffdc - FFDC PEL data 303 */ 304 void event(EventType i_event, std::map<std::string, std::string>& i_additional, 305 const std::vector<util::FFDCFile>& i_ffdc) 306 { 307 bool eventValid = false; // assume no event created 308 bool tiEvent = false; // assume not a terminate event 309 310 std::string eventName; 311 312 switch (i_event) 313 { 314 case EventType::Checkstop: 315 eventName = "org.open_power.HwDiags.Error.Checkstop"; 316 eventValid = true; 317 break; 318 case EventType::Terminate: 319 eventName = "org.open_power.Attn.Error.Terminate"; 320 eventValid = true; 321 tiEvent = true; 322 break; 323 case EventType::Vital: 324 eventName = "org.open_power.Attn.Error.Vital"; 325 eventValid = true; 326 break; 327 case EventType::HwDiagsFail: 328 case EventType::AttentionFail: 329 eventName = "org.open_power.Attn.Error.Fail"; 330 eventValid = true; 331 break; 332 default: 333 eventValid = false; 334 break; 335 } 336 337 if (true == eventValid) 338 { 339 // Create PEL with additional data and FFDC data. The newly created 340 // PEL's platform log-id will be returned. 341 auto pelId = 342 createPel(eventName, i_additional, createFFDCTuples(i_ffdc)); 343 344 // If this is a TI event we will create an additional PEL that is 345 // specific to the subsystem that generated the TI. 346 if ((true == tiEvent) && (0 != pelId)) 347 { 348 // get file descriptor and size of information PEL 349 int pelFd = getPel(pelId); 350 351 // if PEL found, read into buffer 352 if (-1 != pelFd) 353 { 354 auto pelSize = lseek(pelFd, 0, SEEK_END); 355 lseek(pelFd, 0, SEEK_SET); 356 357 // read information PEL into buffer 358 std::vector<uint8_t> buffer(pelSize); 359 size_t numBytes = read(pelFd, buffer.data(), buffer.size()); 360 if (buffer.size() != numBytes) 361 { 362 std::stringstream traceMsg; 363 traceMsg << "Error reading event log: " << (int)numBytes 364 << " of " << (int)buffer.size() << " bytes read"; 365 auto strobj = traceMsg.str(); 366 trace<level::ERROR>(strobj.c_str()); 367 } 368 else 369 { 370 // create PEL from buffer 371 createPelCustom(buffer, i_additional); 372 } 373 374 close(pelFd); 375 } 376 } 377 } 378 } 379 380 /** 381 * Commit special attention TI event to log 382 * 383 * Create a event log with provided additional information and standard 384 * FFDC data plus TI FFDC data 385 * 386 * @param i_additional - Additional log data 387 * @param i_ti_InfoData - TI FFDC data 388 */ 389 void eventTerminate(std::map<std::string, std::string> i_additionalData, 390 char* i_tiInfoData) 391 { 392 393 uint32_t tiInfoSize = 0; // assume TI info was not available 394 395 if (nullptr != i_tiInfoData) 396 { 397 tiInfoSize = 56; // assume not hypervisor TI 398 399 uint8_t subsystem = std::stoi(i_additionalData["Subsystem"]); 400 401 // If hypervisor 402 if (static_cast<uint8_t>(pel::SubsystemID::hypervisor) == subsystem) 403 { 404 tiInfoSize = 1024; // assume hypervisor max 405 406 // hypervisor may just want some of the data 407 if (0 == (*(i_tiInfoData + 0x09) & 0x01)) 408 { 409 uint32_t* additionalLength = (uint32_t*)(i_tiInfoData + 0x50); 410 uint32_t tiAdditional = be32toh(*additionalLength); 411 tiInfoSize = std::min(tiInfoSize, (84 + tiAdditional)); 412 } 413 } 414 } 415 416 std::string traceMsg = "TI info size = " + std::to_string(tiInfoSize); 417 trace<level::INFO>(traceMsg.c_str()); 418 419 event(EventType::Terminate, i_additionalData, 420 createFFDCFiles(i_tiInfoData, tiInfoSize)); 421 } 422 423 /** @brief Commit SBE vital event to log */ 424 void eventVital() 425 { 426 // Additional data for log 427 std::map<std::string, std::string> additionalData; 428 429 // Create log event with additional data and FFDC data 430 event(EventType::Vital, additionalData, createFFDCFiles(nullptr, 0)); 431 } 432 433 /** 434 * Commit attention handler failure event to log 435 * 436 * Create an event log containing the specified error code. 437 * 438 * @param i_error - Error code 439 */ 440 void eventAttentionFail(int i_error) 441 { 442 // Additional data for log 443 std::map<std::string, std::string> additionalData; 444 additionalData["ERROR_CODE"] = std::to_string(i_error); 445 446 // Create log event with additional data and FFDC data 447 event(EventType::AttentionFail, additionalData, 448 createFFDCFiles(nullptr, 0)); 449 } 450 451 /** 452 * Parse systemd journal message field 453 * 454 * Parse the journal looking for the specified field and return the journal 455 * data for that field. 456 * 457 * @param journal - The journal to parse 458 * @param field - Field containing the data to retrieve 459 * @return Data for the speciefied field 460 */ 461 std::string sdjGetFieldValue(sd_journal* journal, const char* field) 462 { 463 const char* data{nullptr}; 464 size_t length{0}; 465 466 // get field value 467 if (0 == sd_journal_get_data(journal, field, (const void**)&data, &length)) 468 { 469 size_t prefix{0}; 470 471 // The data returned by sd_journal_get_data will be prefixed with the 472 // field name and "=" 473 const void* eq = memchr(data, '=', length); 474 if (nullptr != eq) 475 { 476 // get just data following the "=" 477 prefix = (const char*)eq - data + 1; 478 } 479 else 480 { 481 // all the data (should not happen) 482 prefix = 0; 483 std::string value{}; // empty string 484 } 485 486 return std::string{data + prefix, length - prefix}; 487 } 488 else 489 { 490 return std::string{}; // empty string 491 } 492 } 493 494 /** 495 * Gather messages from the journal 496 * 497 * Fetch journal entry data for all entries with the specified field equal to 498 * the specified value. 499 * 500 * @param field - Field to search on 501 * @param fieldValue - Value to search for 502 * @param max - Maximum number of messages fetch 503 * @return Vector of journal entry data 504 */ 505 std::vector<std::string> sdjGetMessages(const std::string& field, 506 const std::string& fieldValue, 507 unsigned int max) 508 { 509 sd_journal* journal; 510 std::vector<std::string> messages; 511 512 if (0 == sd_journal_open(&journal, SD_JOURNAL_LOCAL_ONLY)) 513 { 514 SD_JOURNAL_FOREACH_BACKWARDS(journal) 515 { 516 // Get input field 517 std::string value = sdjGetFieldValue(journal, field.c_str()); 518 519 // Compare field value and read data 520 if (value == fieldValue) 521 { 522 // Get SYSLOG_IDENTIFIER field (process that logged message) 523 std::string syslog = 524 sdjGetFieldValue(journal, "SYSLOG_IDENTIFIER"); 525 526 // Get _PID field 527 std::string pid = sdjGetFieldValue(journal, "_PID"); 528 529 // Get MESSAGE field 530 std::string message = sdjGetFieldValue(journal, "MESSAGE"); 531 532 // Get timestamp 533 uint64_t usec{0}; 534 if (0 == sd_journal_get_realtime_usec(journal, &usec)) 535 { 536 537 // Convert realtime microseconds to date format 538 char dateBuffer[80]; 539 std::string date; 540 std::time_t timeInSecs = usec / 1000000; 541 strftime(dateBuffer, sizeof(dateBuffer), "%b %d %H:%M:%S", 542 std::localtime(&timeInSecs)); 543 date = dateBuffer; 544 545 // Store value to messages 546 value = date + " " + syslog + "[" + pid + "]: " + message; 547 messages.insert(messages.begin(), value); 548 } 549 } 550 551 // limit maximum number of messages 552 if (messages.size() >= max) 553 { 554 break; 555 } 556 } 557 558 sd_journal_close(journal); // close journal when done 559 } 560 561 return messages; 562 } 563 564 } // namespace attn 565