1 #include "config.h" 2 3 #include "log_manager.hpp" 4 5 #include "elog_entry.hpp" 6 #include "elog_meta.hpp" 7 #include "elog_serialize.hpp" 8 #include "extensions.hpp" 9 10 #include <poll.h> 11 #include <sys/inotify.h> 12 #include <systemd/sd-bus.h> 13 #include <systemd/sd-journal.h> 14 #include <unistd.h> 15 16 #include <cassert> 17 #include <chrono> 18 #include <cstdio> 19 #include <cstring> 20 #include <fstream> 21 #include <functional> 22 #include <future> 23 #include <iostream> 24 #include <map> 25 #include <phosphor-logging/log.hpp> 26 #include <sdbusplus/vtable.hpp> 27 #include <set> 28 #include <string> 29 #include <vector> 30 31 using namespace phosphor::logging; 32 using namespace std::chrono; 33 extern const std::map<metadata::Metadata, 34 std::function<metadata::associations::Type>> 35 meta; 36 37 namespace phosphor 38 { 39 namespace logging 40 { 41 namespace internal 42 { 43 inline auto getLevel(const std::string& errMsg) 44 { 45 auto reqLevel = Entry::Level::Error; // Default to Error 46 47 auto levelmap = g_errLevelMap.find(errMsg); 48 if (levelmap != g_errLevelMap.end()) 49 { 50 reqLevel = static_cast<Entry::Level>(levelmap->second); 51 } 52 53 return reqLevel; 54 } 55 56 int Manager::getRealErrSize() 57 { 58 return realErrors.size(); 59 } 60 61 int Manager::getInfoErrSize() 62 { 63 return infoErrors.size(); 64 } 65 66 void Manager::commit(uint64_t transactionId, std::string errMsg) 67 { 68 auto level = getLevel(errMsg); 69 _commit(transactionId, std::move(errMsg), level); 70 } 71 72 void Manager::commitWithLvl(uint64_t transactionId, std::string errMsg, 73 uint32_t errLvl) 74 { 75 _commit(transactionId, std::move(errMsg), 76 static_cast<Entry::Level>(errLvl)); 77 } 78 79 void Manager::_commit(uint64_t transactionId, std::string&& errMsg, 80 Entry::Level errLvl) 81 { 82 if (!Extensions::disableDefaultLogCaps()) 83 { 84 if (errLvl < Entry::sevLowerLimit) 85 { 86 if (realErrors.size() >= ERROR_CAP) 87 { 88 erase(realErrors.front()); 89 } 90 } 91 else 92 { 93 if (infoErrors.size() >= ERROR_INFO_CAP) 94 { 95 erase(infoErrors.front()); 96 } 97 } 98 } 99 100 constexpr const auto transactionIdVar = "TRANSACTION_ID"; 101 // Length of 'TRANSACTION_ID' string. 102 constexpr const auto transactionIdVarSize = std::strlen(transactionIdVar); 103 // Length of 'TRANSACTION_ID=' string. 104 constexpr const auto transactionIdVarOffset = transactionIdVarSize + 1; 105 106 // Flush all the pending log messages into the journal 107 journalSync(); 108 109 sd_journal* j = nullptr; 110 int rc = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); 111 if (rc < 0) 112 { 113 logging::log<logging::level::ERR>( 114 "Failed to open journal", 115 logging::entry("DESCRIPTION=%s", strerror(-rc))); 116 return; 117 } 118 119 std::string transactionIdStr = std::to_string(transactionId); 120 std::set<std::string> metalist; 121 auto metamap = g_errMetaMap.find(errMsg); 122 if (metamap != g_errMetaMap.end()) 123 { 124 metalist.insert(metamap->second.begin(), metamap->second.end()); 125 } 126 127 // Add _PID field information in AdditionalData. 128 metalist.insert("_PID"); 129 130 std::vector<std::string> additionalData; 131 132 // Read the journal from the end to get the most recent entry first. 133 // The result from the sd_journal_get_data() is of the form VARIABLE=value. 134 SD_JOURNAL_FOREACH_BACKWARDS(j) 135 { 136 const char* data = nullptr; 137 size_t length = 0; 138 139 // Look for the transaction id metadata variable 140 rc = sd_journal_get_data(j, transactionIdVar, (const void**)&data, 141 &length); 142 if (rc < 0) 143 { 144 // This journal entry does not have the TRANSACTION_ID 145 // metadata variable. 146 continue; 147 } 148 149 // journald does not guarantee that sd_journal_get_data() returns NULL 150 // terminated strings, so need to specify the size to use to compare, 151 // use the returned length instead of anything that relies on NULL 152 // terminators like strlen(). 153 // The data variable is in the form of 'TRANSACTION_ID=1234'. Remove 154 // the TRANSACTION_ID characters plus the (=) sign to do the comparison. 155 // 'data + transactionIdVarOffset' will be in the form of '1234'. 156 // 'length - transactionIdVarOffset' will be the length of '1234'. 157 if ((length <= (transactionIdVarOffset)) || 158 (transactionIdStr.compare(0, transactionIdStr.size(), 159 data + transactionIdVarOffset, 160 length - transactionIdVarOffset) != 0)) 161 { 162 // The value of the TRANSACTION_ID metadata is not the requested 163 // transaction id number. 164 continue; 165 } 166 167 // Search for all metadata variables in the current journal entry. 168 for (auto i = metalist.cbegin(); i != metalist.cend();) 169 { 170 rc = sd_journal_get_data(j, (*i).c_str(), (const void**)&data, 171 &length); 172 if (rc < 0) 173 { 174 // Metadata variable not found, check next metadata variable. 175 i++; 176 continue; 177 } 178 179 // Metadata variable found, save it and remove it from the set. 180 additionalData.emplace_back(data, length); 181 i = metalist.erase(i); 182 } 183 if (metalist.empty()) 184 { 185 // All metadata variables found, break out of journal loop. 186 break; 187 } 188 } 189 if (!metalist.empty()) 190 { 191 // Not all the metadata variables were found in the journal. 192 for (auto& metaVarStr : metalist) 193 { 194 logging::log<logging::level::INFO>( 195 "Failed to find metadata", 196 logging::entry("META_FIELD=%s", metaVarStr.c_str())); 197 } 198 } 199 200 sd_journal_close(j); 201 202 // Create error Entry dbus object 203 entryId++; 204 if (errLvl >= Entry::sevLowerLimit) 205 { 206 infoErrors.push_back(entryId); 207 } 208 else 209 { 210 realErrors.push_back(entryId); 211 } 212 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>( 213 std::chrono::system_clock::now().time_since_epoch()) 214 .count(); 215 auto objPath = std::string(OBJ_ENTRY) + '/' + std::to_string(entryId); 216 217 AssociationList objects{}; 218 processMetadata(errMsg, additionalData, objects); 219 220 auto e = std::make_unique<Entry>(busLog, objPath, entryId, 221 ms, // Milliseconds since 1970 222 errLvl, std::move(errMsg), 223 std::move(additionalData), 224 std::move(objects), fwVersion, *this); 225 serialize(*e); 226 227 doExtensionLogCreate(*e); 228 229 entries.insert(std::make_pair(entryId, std::move(e))); 230 } 231 232 void Manager::doExtensionLogCreate(const Entry& entry) 233 { 234 // Make the association <endpointpath>/<endpointtype> paths 235 std::vector<std::string> assocs; 236 for (const auto& [forwardType, reverseType, endpoint] : 237 entry.associations()) 238 { 239 std::string e{endpoint}; 240 e += '/' + reverseType; 241 assocs.push_back(e); 242 } 243 244 for (auto& create : Extensions::getCreateFunctions()) 245 { 246 try 247 { 248 create(entry.message(), entry.id(), entry.timestamp(), 249 entry.severity(), entry.additionalData(), assocs); 250 } 251 catch (std::exception& e) 252 { 253 log<level::ERR>("An extension's create function threw an exception", 254 phosphor::logging::entry("ERROR=%s", e.what())); 255 } 256 } 257 } 258 259 void Manager::processMetadata(const std::string& errorName, 260 const std::vector<std::string>& additionalData, 261 AssociationList& objects) const 262 { 263 // additionalData is a list of "metadata=value" 264 constexpr auto separator = '='; 265 for (const auto& entryItem : additionalData) 266 { 267 auto found = entryItem.find(separator); 268 if (std::string::npos != found) 269 { 270 auto metadata = entryItem.substr(0, found); 271 auto iter = meta.find(metadata); 272 if (meta.end() != iter) 273 { 274 (iter->second)(metadata, additionalData, objects); 275 } 276 } 277 } 278 } 279 280 void Manager::erase(uint32_t entryId) 281 { 282 auto entryFound = entries.find(entryId); 283 if (entries.end() != entryFound) 284 { 285 for (auto& func : Extensions::getDeleteProhibitedFunctions()) 286 { 287 try 288 { 289 bool prohibited = false; 290 func(entryId, prohibited); 291 if (prohibited) 292 { 293 // Future work remains to throw an error here. 294 return; 295 } 296 } 297 catch (std::exception& e) 298 { 299 log<level::ERR>( 300 "An extension's deleteProhibited function threw " 301 "an exception", 302 entry("ERROR=%s", e.what())); 303 } 304 } 305 306 // Delete the persistent representation of this error. 307 fs::path errorPath(ERRLOG_PERSIST_PATH); 308 errorPath /= std::to_string(entryId); 309 fs::remove(errorPath); 310 311 auto removeId = [](std::list<uint32_t>& ids, uint32_t id) { 312 auto it = std::find(ids.begin(), ids.end(), id); 313 if (it != ids.end()) 314 { 315 ids.erase(it); 316 } 317 }; 318 if (entryFound->second->severity() >= Entry::sevLowerLimit) 319 { 320 removeId(infoErrors, entryId); 321 } 322 else 323 { 324 removeId(realErrors, entryId); 325 } 326 entries.erase(entryFound); 327 328 for (auto& remove : Extensions::getDeleteFunctions()) 329 { 330 try 331 { 332 remove(entryId); 333 } 334 catch (std::exception& e) 335 { 336 log<level::ERR>("An extension's delete function threw an " 337 "exception", 338 entry("ERROR=%s", e.what())); 339 } 340 } 341 } 342 else 343 { 344 logging::log<level::ERR>("Invalid entry ID to delete", 345 logging::entry("ID=%d", entryId)); 346 } 347 } 348 349 void Manager::restore() 350 { 351 auto sanity = [](const auto& id, const auto& restoredId) { 352 return id == restoredId; 353 }; 354 std::vector<uint32_t> errorIds; 355 356 fs::path dir(ERRLOG_PERSIST_PATH); 357 if (!fs::exists(dir) || fs::is_empty(dir)) 358 { 359 return; 360 } 361 362 for (auto& file : fs::directory_iterator(dir)) 363 { 364 auto id = file.path().filename().c_str(); 365 auto idNum = std::stol(id); 366 auto e = std::make_unique<Entry>( 367 busLog, std::string(OBJ_ENTRY) + '/' + id, idNum, *this); 368 if (deserialize(file.path(), *e)) 369 { 370 // validate the restored error entry id 371 if (sanity(static_cast<uint32_t>(idNum), e->id())) 372 { 373 e->emit_object_added(); 374 if (e->severity() >= Entry::sevLowerLimit) 375 { 376 infoErrors.push_back(idNum); 377 } 378 else 379 { 380 realErrors.push_back(idNum); 381 } 382 383 entries.insert(std::make_pair(idNum, std::move(e))); 384 errorIds.push_back(idNum); 385 } 386 else 387 { 388 logging::log<logging::level::ERR>( 389 "Failed in sanity check while restoring error entry. " 390 "Ignoring error entry", 391 logging::entry("ID_NUM=%d", idNum), 392 logging::entry("ENTRY_ID=%d", e->id())); 393 } 394 } 395 } 396 397 if (!errorIds.empty()) 398 { 399 entryId = *(std::max_element(errorIds.begin(), errorIds.end())); 400 } 401 } 402 403 void Manager::journalSync() 404 { 405 bool syncRequested = false; 406 auto fd = -1; 407 auto rc = -1; 408 auto wd = -1; 409 auto bus = sdbusplus::bus::new_default(); 410 411 auto start = 412 duration_cast<microseconds>(steady_clock::now().time_since_epoch()) 413 .count(); 414 415 constexpr auto maxRetry = 2; 416 for (int i = 0; i < maxRetry; i++) 417 { 418 // Read timestamp from synced file 419 constexpr auto syncedPath = "/run/systemd/journal/synced"; 420 std::ifstream syncedFile(syncedPath); 421 if (syncedFile.fail()) 422 { 423 // If the synced file doesn't exist, a sync request will create it. 424 if (errno != ENOENT) 425 { 426 log<level::ERR>("Failed to open journal synced file", 427 entry("FILENAME=%s", syncedPath), 428 entry("ERRNO=%d", errno)); 429 return; 430 } 431 } 432 else 433 { 434 // Only read the synced file if it exists. 435 // See if a sync happened by now 436 std::string timestampStr; 437 std::getline(syncedFile, timestampStr); 438 auto timestamp = std::stoll(timestampStr); 439 if (timestamp >= start) 440 { 441 return; 442 } 443 } 444 445 // Let's ask for a sync, but only once 446 if (!syncRequested) 447 { 448 syncRequested = true; 449 450 constexpr auto JOURNAL_UNIT = "systemd-journald.service"; 451 auto signal = SIGRTMIN + 1; 452 453 auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, 454 SYSTEMD_INTERFACE, "KillUnit"); 455 method.append(JOURNAL_UNIT, "main", signal); 456 bus.call(method); 457 if (method.is_method_error()) 458 { 459 log<level::ERR>("Failed to kill journal service"); 460 return; 461 } 462 continue; 463 } 464 465 // Let's install the inotify watch, if we didn't do that yet. This watch 466 // monitors the syncedFile for when journald updates it with a newer 467 // timestamp. This means the journal has been flushed. 468 if (fd < 0) 469 { 470 fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 471 if (fd < 0) 472 { 473 log<level::ERR>("Failed to create inotify watch", 474 entry("ERRNO=%d", errno)); 475 return; 476 } 477 478 constexpr auto JOURNAL_RUN_PATH = "/run/systemd/journal"; 479 wd = inotify_add_watch(fd, JOURNAL_RUN_PATH, 480 IN_MOVED_TO | IN_DONT_FOLLOW | IN_ONLYDIR); 481 if (wd < 0) 482 { 483 log<level::ERR>("Failed to watch journal directory", 484 entry("PATH=%s", JOURNAL_RUN_PATH), 485 entry("ERRNO=%d", errno)); 486 close(fd); 487 return; 488 } 489 continue; 490 } 491 492 // Let's wait until inotify reports an event 493 struct pollfd fds = { 494 .fd = fd, 495 .events = POLLIN, 496 }; 497 constexpr auto pollTimeout = 5; // 5 seconds 498 rc = poll(&fds, 1, pollTimeout * 1000); 499 if (rc < 0) 500 { 501 log<level::ERR>("Failed to add event", entry("ERRNO=%d", errno), 502 entry("ERR=%s", strerror(-rc))); 503 inotify_rm_watch(fd, wd); 504 close(fd); 505 return; 506 } 507 else if (rc == 0) 508 { 509 log<level::INFO>("Poll timeout, no new journal synced data", 510 entry("TIMEOUT=%d", pollTimeout)); 511 break; 512 } 513 514 // Read from the specified file descriptor until there is no new data, 515 // throwing away everything read since the timestamp will be read at the 516 // beginning of the loop. 517 constexpr auto maxBytes = 64; 518 uint8_t buffer[maxBytes]; 519 while (read(fd, buffer, maxBytes) > 0) 520 ; 521 } 522 523 if (fd != -1) 524 { 525 if (wd != -1) 526 { 527 inotify_rm_watch(fd, wd); 528 } 529 close(fd); 530 } 531 532 return; 533 } 534 535 std::string Manager::readFWVersion() 536 { 537 std::string version; 538 std::ifstream versionFile{BMC_VERSION_FILE}; 539 std::string line; 540 static constexpr auto VERSION_ID = "VERSION_ID="; 541 542 while (std::getline(versionFile, line)) 543 { 544 if (line.find(VERSION_ID) != std::string::npos) 545 { 546 auto pos = line.find_first_of('"') + 1; 547 version = line.substr(pos, line.find_last_of('"') - pos); 548 break; 549 } 550 } 551 552 if (version.empty()) 553 { 554 log<level::ERR>("Unable to read BMC firmware version"); 555 } 556 557 return version; 558 } 559 560 } // namespace internal 561 } // namespace logging 562 } // namespace phosphor 563