blob: 493b443368ed6a6cb9229dd7592ea56c57785333 [file] [log] [blame]
/*
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
#include <systemd/sd-journal.h>
#include <boost/algorithm/string.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/container/flat_map.hpp>
#include <boost/container/flat_set.hpp>
#include <pulse_event_monitor.hpp>
#include <sdbusplus/asio/object_server.hpp>
#include <sel_logger.hpp>
#include <threshold_event_monitor.hpp>
#include <watchdog_event_monitor.hpp>
#ifdef SEL_LOGGER_ENABLE_SEL_DELETE
#include <xyz/openbmc_project/Common/error.hpp>
#endif
#ifdef SEL_LOGGER_MONITOR_THRESHOLD_ALARM_EVENTS
#include <threshold_alarm_event_monitor.hpp>
#endif
#ifdef SEL_LOGGER_MONITOR_HOST_ERROR_EVENTS
#include <host_error_event_monitor.hpp>
#endif
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
struct DBusInternalError final : public sdbusplus::exception_t
{
const char* name() const noexcept override
{
return "org.freedesktop.DBus.Error.Failed";
}
const char* description() const noexcept override
{
return "internal error";
}
const char* what() const noexcept override
{
return "org.freedesktop.DBus.Error.Failed: "
"internal error";
}
int get_errno() const noexcept override
{
return EACCES;
}
};
#ifndef SEL_LOGGER_SEND_TO_LOGGING_SERVICE
static bool getSELLogFiles(std::vector<std::filesystem::path>& selLogFiles)
{
// Loop through the directory looking for ipmi_sel log files
for (const std::filesystem::directory_entry& dirEnt :
std::filesystem::directory_iterator(selLogDir))
{
std::string filename = dirEnt.path().filename();
if (boost::starts_with(filename, selLogFilename))
{
// If we find an ipmi_sel log file, save the path
selLogFiles.emplace_back(selLogDir / filename);
}
}
// As the log files rotate, they are appended with a ".#" that is higher for
// the older logs. Since we don't expect more than 10 log files, we
// can just sort the list to get them in order from newest to oldest
std::sort(selLogFiles.begin(), selLogFiles.end());
return !selLogFiles.empty();
}
static void saveClearSelTimestamp()
{
int fd = open("/var/lib/ipmi/sel_erase_time",
O_WRONLY | O_CREAT | O_CLOEXEC, 0644);
if (fd < 0)
{
std::cerr << "Failed to open file\n";
return;
}
if (futimens(fd, NULL) < 0)
{
std::cerr << "Failed to update SEL cleared timestamp: "
<< std::string(strerror(errno));
}
close(fd);
}
#ifdef SEL_LOGGER_ENABLE_SEL_DELETE
std::vector<uint16_t> nextRecordsCache;
static void backupCacheToFile()
{
std::ofstream nextRecordStream(selLogDir / nextRecordFilename);
for (auto recordIds : nextRecordsCache)
{
nextRecordStream << recordIds << '\n';
}
}
static uint16_t getNewRecordId()
{
uint16_t nextRecordId = nextRecordsCache.back();
// Check if SEL is full
if (nextRecordId == selInvalidRecID)
{
return nextRecordId;
}
nextRecordsCache.pop_back();
if (nextRecordsCache.empty())
{
nextRecordsCache.push_back(nextRecordId + 1);
}
backupCacheToFile();
return nextRecordId;
}
static void initializeRecordId()
{
std::ifstream nextRecordStream(selLogDir / nextRecordFilename);
if (!nextRecordStream.is_open())
{
std::ofstream newStream(selLogDir / nextRecordFilename);
newStream << '1' << '\n';
newStream.close();
nextRecordStream.open(selLogDir / nextRecordFilename);
}
std::string line;
while (std::getline(nextRecordStream, line))
{
nextRecordsCache.push_back(std::stoi(line));
}
}
void clearSelLogFiles()
{
saveClearSelTimestamp();
// Clear the SEL by deleting the log files
std::vector<std::filesystem::path> selLogFiles;
if (getSELLogFiles(selLogFiles))
{
for (const std::filesystem::path& file : selLogFiles)
{
std::error_code ec;
std::filesystem::remove(file, ec);
}
}
// Reload rsyslog so it knows to start new log files
boost::asio::io_context io;
auto dbus = std::make_shared<sdbusplus::asio::connection>(io);
sdbusplus::message_t rsyslogReload = dbus->new_method_call(
"org.freedesktop.systemd1", "/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager", "ReloadUnit");
rsyslogReload.append("rsyslog.service", "replace");
try
{
sdbusplus::message_t reloadResponse = dbus->call(rsyslogReload);
}
catch (const sdbusplus::exception_t& e)
{
std::cerr << e.what() << "\n";
}
// Set next record to 1
nextRecordsCache.clear();
nextRecordsCache.push_back(1);
// Update backup file as well
std::ofstream nextRecordStream(selLogDir / nextRecordFilename);
nextRecordStream << '1' << '\n';
}
static bool selDeleteTargetRecord(const uint16_t& targetId)
{
bool targetEntryFound = false;
// Check if the ipmi_sel exist and save the path
std::vector<std::filesystem::path> selLogFiles;
if (!getSELLogFiles(selLogFiles))
{
return targetEntryFound;
}
// Go over all the ipmi_sel files to remove the entry with the target ID
for (const std::filesystem::path& file : selLogFiles)
{
std::fstream logStream(file, std::ios::in);
std::fstream tempFile(selLogDir / "temp", std::ios::out);
if (!logStream.is_open())
{
return targetEntryFound;
}
std::string line;
while (std::getline(logStream, line))
{
// Get the recordId of the current entry
int left = line.find(" ");
int right = line.find(",");
int recordLen = right - left;
std::string recordId = line.substr(left, recordLen);
int newRecordId = std::stoi(recordId);
if (newRecordId != targetId)
{
// Copy the entry from the original ipmi_sel to the temp file
tempFile << line << '\n';
}
else
{
// Skip copying the target entry
targetEntryFound = true;
}
}
logStream.close();
tempFile.close();
if (targetEntryFound)
{
std::fstream logStream(file, std::ios::out);
std::fstream tempFile(selLogDir / "temp", std::ios::in);
while (std::getline(tempFile, line))
{
logStream << line << '\n';
}
logStream.close();
tempFile.close();
std::error_code ec;
if (!std::filesystem::remove(selLogDir / "temp", ec))
{
std::cerr << ec.message() << std::endl;
}
break;
}
}
return targetEntryFound;
}
static void selDeleteRecord(const uint16_t& recordId)
{
std::filesystem::file_time_type prevAddTime =
std::filesystem::last_write_time(selLogDir / selLogFilename);
bool targetEntryFound = selDeleteTargetRecord(recordId);
// Check if the Record Id was found
if (!targetEntryFound)
{
throw sdbusplus::xyz::openbmc_project::Common::Error::
ResourceNotFound();
}
// Add to next record cache for reuse
nextRecordsCache.push_back(recordId);
// Add to backup file
std::ofstream nextRecordStream(selLogDir / nextRecordFilename,
std::ios::app);
nextRecordStream << recordId << '\n';
// Keep Last Add Time the same
std::filesystem::last_write_time(selLogDir / selLogFilename, prevAddTime);
// Update Last Del Time
saveClearSelTimestamp();
}
#else
static unsigned int initializeRecordId()
{
std::vector<std::filesystem::path> selLogFiles;
if (!getSELLogFiles(selLogFiles))
{
return 0;
}
std::ifstream logStream(selLogFiles.front());
if (!logStream.is_open())
{
return 0;
}
std::string line;
std::string newestEntry;
while (std::getline(logStream, line))
{
newestEntry = line;
}
std::vector<std::string> newestEntryFields;
boost::split(newestEntryFields, newestEntry, boost::is_any_of(" ,"),
boost::token_compress_on);
if (newestEntryFields.size() < 4)
{
return 0;
}
return std::stoul(newestEntryFields[1]);
}
static unsigned int recordId = initializeRecordId();
static unsigned int getNewRecordId()
{
if (++recordId >= selInvalidRecID)
{
recordId = selInvalidRecID;
}
return recordId;
}
void clearSelLogFiles()
{
saveClearSelTimestamp();
// Clear the SEL by deleting the log files
std::vector<std::filesystem::path> selLogFiles;
if (getSELLogFiles(selLogFiles))
{
for (const std::filesystem::path& file : selLogFiles)
{
std::error_code ec;
std::filesystem::remove(file, ec);
}
}
recordId = 0;
// Reload rsyslog so it knows to start new log files
boost::asio::io_context io;
auto dbus = std::make_shared<sdbusplus::asio::connection>(io);
sdbusplus::message_t rsyslogReload = dbus->new_method_call(
"org.freedesktop.systemd1", "/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager", "ReloadUnit");
rsyslogReload.append("rsyslog.service", "replace");
try
{
sdbusplus::message_t reloadResponse = dbus->call(rsyslogReload);
}
catch (const sdbusplus::exception_t& e)
{
std::cerr << e.what() << "\n";
}
}
#endif
#endif
static void toHexStr(const std::vector<uint8_t>& data, std::string& hexStr)
{
std::stringstream stream;
stream << std::hex << std::uppercase << std::setfill('0');
for (int v : data)
{
stream << std::setw(2) << v;
}
hexStr = stream.str();
}
template <typename... T>
static uint16_t selAddSystemRecord(
[[maybe_unused]] std::shared_ptr<sdbusplus::asio::connection> conn,
[[maybe_unused]] const std::string& message, const std::string& path,
const std::vector<uint8_t>& selData, const bool& assert,
const uint16_t& genId, [[maybe_unused]] T&&... metadata)
{
// Only 3 bytes of SEL event data are allowed in a system record
if (selData.size() > selEvtDataMaxSize)
{
throw std::invalid_argument("Event data too large");
}
std::string selDataStr;
toHexStr(selData, selDataStr);
#ifdef SEL_LOGGER_SEND_TO_LOGGING_SERVICE
sdbusplus::message_t AddToLog = conn->new_method_call(
"xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging",
"xyz.openbmc_project.Logging.Create", "Create");
std::string journalMsg(
message + " from " + path + ": " +
" RecordType=" + std::to_string(selSystemType) +
", GeneratorID=" + std::to_string(genId) +
", EventDir=" + std::to_string(assert) + ", EventData=" + selDataStr);
AddToLog.append(
journalMsg, "xyz.openbmc_project.Logging.Entry.Level.Informational",
std::map<std::string, std::string>(
{{"SENSOR_PATH", path},
{"GENERATOR_ID", std::to_string(genId)},
{"RECORD_TYPE", std::to_string(selSystemType)},
{"EVENT_DIR", std::to_string(assert)},
{"SENSOR_DATA", selDataStr}}));
conn->call(AddToLog);
return 0;
#else
unsigned int recordId = getNewRecordId();
if (recordId < selInvalidRecID)
{
sd_journal_send(
"MESSAGE=%s", message.c_str(), "PRIORITY=%i", selPriority,
"MESSAGE_ID=%s", selMessageId, "IPMI_SEL_RECORD_ID=%d", recordId,
"IPMI_SEL_RECORD_TYPE=%x", selSystemType,
"IPMI_SEL_GENERATOR_ID=%x", genId, "IPMI_SEL_SENSOR_PATH=%s",
path.c_str(), "IPMI_SEL_EVENT_DIR=%x", assert, "IPMI_SEL_DATA=%s",
selDataStr.c_str(), std::forward<T>(metadata)..., NULL);
}
return recordId;
#endif
}
static uint16_t selAddOemRecord(
[[maybe_unused]] std::shared_ptr<sdbusplus::asio::connection> conn,
[[maybe_unused]] const std::string& message,
const std::vector<uint8_t>& selData, const uint8_t& recordType)
{
// A maximum of 13 bytes of SEL event data are allowed in an OEM record
if (selData.size() > selOemDataMaxSize)
{
throw std::invalid_argument("Event data too large");
}
std::string selDataStr;
toHexStr(selData, selDataStr);
#ifdef SEL_LOGGER_SEND_TO_LOGGING_SERVICE
sdbusplus::message_t AddToLog = conn->new_method_call(
"xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging",
"xyz.openbmc_project.Logging.Create", "Create");
std::string journalMsg(
message + ": " + " RecordType=" + std::to_string(recordType) +
", GeneratorID=" + std::to_string(0) +
", EventDir=" + std::to_string(0) + ", EventData=" + selDataStr);
AddToLog.append(
journalMsg, "xyz.openbmc_project.Logging.Entry.Level.Informational",
std::map<std::string, std::string>(
{{"SENSOR_PATH", ""},
{"GENERATOR_ID", std::to_string(0)},
{"RECORD_TYPE", std::to_string(recordType)},
{"EVENT_DIR", std::to_string(0)},
{"SENSOR_DATA", selDataStr}}));
conn->call(AddToLog);
return 0;
#else
unsigned int recordId = getNewRecordId();
if (recordId < selInvalidRecID)
{
sd_journal_send("MESSAGE=%s", message.c_str(), "PRIORITY=%i",
selPriority, "MESSAGE_ID=%s", selMessageId,
"IPMI_SEL_RECORD_ID=%d", recordId,
"IPMI_SEL_RECORD_TYPE=%x", recordType,
"IPMI_SEL_DATA=%s", selDataStr.c_str(), NULL);
}
return recordId;
#endif
}
int main(int, char*[])
{
#ifndef SEL_LOGGER_SEND_TO_LOGGING_SERVICE
#ifdef SEL_LOGGER_ENABLE_SEL_DELETE
initializeRecordId();
#endif
#endif
// setup connection to dbus
boost::asio::io_context io;
auto conn = std::make_shared<sdbusplus::asio::connection>(io);
// IPMI SEL Object
conn->request_name(ipmiSelObject);
auto server = sdbusplus::asio::object_server(conn);
// Add SEL Interface
std::shared_ptr<sdbusplus::asio::dbus_interface> ifaceAddSel =
server.add_interface(ipmiSelPath, ipmiSelAddInterface);
// Add a new SEL entry
ifaceAddSel->register_method(
"IpmiSelAdd",
[conn](const std::string& message, const std::string& path,
const std::vector<uint8_t>& selData, const bool& assert,
const uint16_t& genId) {
return selAddSystemRecord(conn, message, path, selData, assert,
genId);
});
// Add a new OEM SEL entry
ifaceAddSel->register_method(
"IpmiSelAddOem",
[conn](const std::string& message, const std::vector<uint8_t>& selData,
const uint8_t& recordType) {
return selAddOemRecord(conn, message, selData, recordType);
});
#ifndef SEL_LOGGER_SEND_TO_LOGGING_SERVICE
// Clear SEL entries
ifaceAddSel->register_method("Clear", []() { clearSelLogFiles(); });
#ifdef SEL_LOGGER_ENABLE_SEL_DELETE
// Delete a SEL entry
ifaceAddSel->register_method("SELDelete", [](const uint16_t& recordId) {
return selDeleteRecord(recordId);
});
#endif
#endif
ifaceAddSel->initialize();
#ifdef SEL_LOGGER_MONITOR_THRESHOLD_EVENTS
sdbusplus::bus::match_t thresholdAssertMonitor =
startThresholdAssertMonitor(conn);
#endif
#ifdef REDFISH_LOG_MONITOR_PULSE_EVENTS
sdbusplus::bus::match_t pulseEventMonitor = startPulseEventMonitor(conn);
#endif
#ifdef SEL_LOGGER_MONITOR_WATCHDOG_EVENTS
sdbusplus::bus::match_t watchdogEventMonitor =
startWatchdogEventMonitor(conn);
#endif
#ifdef SEL_LOGGER_MONITOR_THRESHOLD_ALARM_EVENTS
startThresholdAlarmMonitor(conn);
#endif
#ifdef SEL_LOGGER_MONITOR_HOST_ERROR_EVENTS
startHostErrorEventMonitor(conn);
#endif
io.run();
return 0;
}