Initial commit
Signed-off-by: Artem Senichev <a.senichev@yadro.com>
diff --git a/src/config.hpp b/src/config.hpp
new file mode 100644
index 0000000..5264a5e
--- /dev/null
+++ b/src/config.hpp
@@ -0,0 +1,41 @@
+/**
+ * @brief Logger configuration.
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * 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.
+ */
+
+#pragma once
+
+/** @struct Config
+ * @brief Represent the configuration of the logger.
+ */
+struct Config
+{
+ /** @brief Path to write log files. */
+ const char* path;
+ /** @brief Storage limit (message count). */
+ int storageSizeLimit;
+ /** @brief Storage limit (max time). */
+ int storageTimeLimit;
+ /** @brief Flush policy: save logs every flushPeriod hours. */
+ int flushPeriod;
+ /** @brief Rotation limit (max files to store). */
+ int rotationLimit;
+};
+
+/** @brief Global logger configuration instance. */
+extern Config loggerConfig;
diff --git a/src/dbus_server.cpp b/src/dbus_server.cpp
new file mode 100644
index 0000000..53a2087
--- /dev/null
+++ b/src/dbus_server.cpp
@@ -0,0 +1,37 @@
+/**
+ * @brief Server side implementation of the D-Bus interface
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * 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 "dbus_server.hpp"
+#include <xyz/openbmc_project/Common/File/error.hpp>
+
+
+DbusServer::DbusServer(LogManager& logManager, sdbusplus::bus::bus& bus, const char* path /*= HOSTLOGGER_DBUS_PATH*/)
+: server_inherit(bus, path),
+ logManager_(logManager)
+{
+}
+
+
+void DbusServer::flush()
+{
+ const int rc = logManager_.flush();
+ if (rc != 0)
+ throw sdbusplus::xyz::openbmc_project::Common::File::Error::Write();
+}
diff --git a/src/dbus_server.hpp b/src/dbus_server.hpp
new file mode 100644
index 0000000..1e6d47c
--- /dev/null
+++ b/src/dbus_server.hpp
@@ -0,0 +1,67 @@
+/**
+ * @brief Server side implementation of the D-Bus interface
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include "log_manager.hpp"
+#include <xyz/openbmc_project/HostLogger/server.hpp>
+
+/** @brief D-Bus interface name. */
+#define HOSTLOGGER_DBUS_IFACE "xyz.openbmc_project.HostLogger"
+/** @brief D-Bus path. */
+#define HOSTLOGGER_DBUS_PATH "/xyz/openbmc_project/HostLogger"
+
+/** @brief unique_ptr functor to release an event reference. */
+struct EventDeleter
+{
+ void operator()(sd_event* event) const
+ {
+ sd_event_unref(event);
+ }
+};
+
+/* @brief Alias 'event' to a unique_ptr type for auto-release. */
+using EventPtr = std::unique_ptr<sd_event, EventDeleter>;
+
+// Typedef for super class
+using server_inherit = sdbusplus::server::object_t<sdbusplus::xyz::openbmc_project::server::HostLogger>;
+
+
+/** @class DbusServer
+ * @brief D-Bus service by host logger.
+ */
+class DbusServer: public server_inherit
+{
+public:
+ /** @brief Constructor.
+ *
+ * @param[in] logManager - log manager
+ * @param[in] bus - bus to attach to
+ * @param[in] path - bath to attach at, optional, default is HOSTLOGGER_DBUS_PATH
+ */
+ DbusServer(LogManager& logManager, sdbusplus::bus::bus& bus, const char* path = HOSTLOGGER_DBUS_PATH);
+
+ // From server_inherit
+ void flush();
+
+private:
+ /** @brief Log manager instance. */
+ LogManager& logManager_;
+};
diff --git a/src/dbus_watch.cpp b/src/dbus_watch.cpp
new file mode 100644
index 0000000..fa1bcb1
--- /dev/null
+++ b/src/dbus_watch.cpp
@@ -0,0 +1,165 @@
+/**
+ * @brief D-Bus signal watcher.
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * 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 "config.hpp"
+#include "dbus_watch.hpp"
+#include <set>
+#include <string>
+#include <chrono>
+
+// D-Bus path to the host state object
+#define DBUS_HOST_OBJECT_PATH "/xyz/openbmc_project/state/host0"
+
+// Macro to normalize SDBus status code:
+// positive code is not an error in the systemd dbus implementation.
+#define DBUS_RC_TO_ERR(c) (c = (c <= 0 ? -c : 0))
+
+
+DbusWatcher::DbusWatcher(LogManager& logManager, sdbusplus::bus::bus& bus)
+: logManager_(logManager),
+ bus_(bus)
+{
+}
+
+
+int DbusWatcher::initialize()
+{
+ int rc;
+
+ // Add IO callback for host's log stream socket
+ rc = sd_event_add_io(bus_.get_event(), NULL,
+ logManager_.getHostLogFd(),
+ EPOLLIN, &DbusWatcher::ioCallback, this);
+ if (DBUS_RC_TO_ERR(rc)) {
+ fprintf(stderr, "Unable to add IO handler: %i %s\n",
+ rc, strerror(rc));
+ return rc;
+ }
+
+ // Add flush handler
+ if (loggerConfig.flushPeriod == 0)
+ registerEventHandler();
+ else
+ rc = registerTimerHandler();
+
+ return rc;
+}
+
+
+void DbusWatcher::registerEventHandler()
+{
+ conds_["xyz.openbmc_project.State.Host"] = {
+ .property = "RequestedHostTransition",
+ .values = {
+ "xyz.openbmc_project.State.Host.Transition.On"
+ }
+ };
+ conds_["xyz.openbmc_project.State.OperatingSystem.Status"] = {
+ .property = "OperatingSystemState",
+ .values = {
+ "xyz.openbmc_project.State.OperatingSystem.Status.OSStatus.BootComplete",
+ "xyz.openbmc_project.State.OperatingSystem.Status.OSStatus.Inactive"
+ }
+ };
+ for (auto& cond: conds_) {
+ cond.second.match = std::make_unique<sdbusplus::bus::match_t>(bus_,
+ sdbusplus::bus::match::rules::propertiesChanged(
+ DBUS_HOST_OBJECT_PATH, cond.first),
+ [this](auto& msg){ this->hostStateHandler(msg); });
+ }
+}
+
+
+int DbusWatcher::registerTimerHandler()
+{
+ int rc;
+ sd_event_source* ev = NULL;
+
+ rc = sd_event_add_time(bus_.get_event(), &ev, CLOCK_MONOTONIC,
+ UINT64_MAX, 0, &DbusWatcher::timerCallback, this);
+ if (DBUS_RC_TO_ERR(rc)) {
+ fprintf(stderr, "Unable to add timer handler: %i %s\n", rc, strerror(rc));
+ return rc;
+ }
+
+ rc = sd_event_source_set_enabled(ev, SD_EVENT_ON);
+ if (DBUS_RC_TO_ERR(rc)) {
+ fprintf(stderr, "Unable to enable timer handler: %i %s\n", rc, strerror(rc));
+ return rc;
+ }
+
+ return setupTimer(ev);
+}
+
+
+int DbusWatcher::setupTimer(sd_event_source* event)
+{
+ // Get the current time and add the delta (flush period)
+ using namespace std::chrono;
+ auto now = steady_clock::now().time_since_epoch();
+ hours timeOut(loggerConfig.flushPeriod);
+ auto expireTime = duration_cast<microseconds>(now) +
+ duration_cast<microseconds>(timeOut);
+
+ //Set the time
+ int rc = sd_event_source_set_time(event, expireTime.count());
+ if (DBUS_RC_TO_ERR(rc))
+ fprintf(stderr, "Unable to set timer handler: %i %s\n", rc, strerror(rc));
+
+ return rc;
+}
+
+
+void DbusWatcher::hostStateHandler(sdbusplus::message::message& msg)
+{
+ std::map<std::string, sdbusplus::message::variant<std::string>> properties;
+ std::string interface;
+
+ msg.read(interface, properties);
+
+ bool needFlush = false;
+ const auto itc = conds_.find(interface);
+ if (itc != conds_.end()) {
+ const auto itp = properties.find(itc->second.property);
+ if (itp != properties.end()) {
+ const auto& propVal = itp->second.get<std::string>();
+ needFlush = itc->second.values.find(propVal) != itc->second.values.end();
+ }
+ }
+
+ if (needFlush)
+ logManager_.flush();
+}
+
+
+int DbusWatcher::ioCallback(sd_event_source* /*event*/, int /*fd*/, uint32_t /*revents*/, void* data)
+{
+ DbusWatcher* instance = static_cast<DbusWatcher*>(data);
+ instance->logManager_.handleHostLog();
+ return 0;
+}
+
+
+int DbusWatcher::timerCallback(sd_event_source* event, uint64_t /*usec*/, void* data)
+{
+ DbusWatcher* instance = static_cast<DbusWatcher*>(data);
+ instance->logManager_.flush();
+ return instance->setupTimer(event);
+}
diff --git a/src/dbus_watch.hpp b/src/dbus_watch.hpp
new file mode 100644
index 0000000..1f5201f
--- /dev/null
+++ b/src/dbus_watch.hpp
@@ -0,0 +1,111 @@
+/**
+ * @brief D-Bus signal watcher.
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include "log_manager.hpp"
+
+#include <set>
+#include <map>
+#include <string>
+
+#include <sdbusplus/bus/match.hpp>
+
+
+
+/** @class DbusServer
+ * @brief D-Bus service by host logger.
+ */
+class DbusWatcher
+{
+public:
+ /** @brief Constructor.
+ *
+ * @param[in] logManager - log manager
+ * @param[in] bus - bus to attach to
+ */
+ DbusWatcher(LogManager& logManager, sdbusplus::bus::bus& bus);
+
+ /** @brief Initialize watcher.
+ *
+ * @return error code, 0 if operation completed successfully
+ */
+ int initialize();
+
+private:
+ /** @brief Register D-Bus event handler. */
+ void registerEventHandler();
+
+ /** @brief Register D-Bus timer handler.
+ *
+ * @return error code, 0 if operation completed successfully
+ */
+ int registerTimerHandler();
+
+ /** @brief Setup D-Bus timer.
+ *
+ * @param[in] event - event source to setup
+ *
+ * @return error code, 0 if operation completed successfully
+ */
+ int setupTimer(sd_event_source* event);
+
+ /** @brief Callback function for host state change.
+ *
+ * @param[in] msg - data associated with subscribed signal
+ */
+ void hostStateHandler(sdbusplus::message::message& msg);
+
+ /** @brief D-Bus IO callback used to handle incoming data on the opened file.
+ * See sd_event_io_handler_t for details.
+ */
+ static int ioCallback(sd_event_source* event, int fd, uint32_t revents, void* data);
+
+ /** @brief D-Bus timer callback used to flush log store.
+ * See sd_event_add_time for details.
+ */
+ static int timerCallback(sd_event_source* event, uint64_t usec, void* data);
+
+private:
+ /** @struct FlushCondition
+ * @brief Describes flush conditions for log manager based on host state event.
+ */
+ struct FlushCondition
+ {
+ /** @brief D-Bus property name to watch. */
+ std::string property;
+ /** @brief Set of possible values for property watched. */
+ std::set<std::string> values;
+ /** @brief Match object to create D-Bus handler. */
+ std::unique_ptr<sdbusplus::bus::match_t> match;
+ };
+
+private:
+ /** @brief Log manager instance. */
+ LogManager& logManager_;
+
+ /** @brief D-Bus bus. */
+ sdbusplus::bus::bus& bus_;
+
+ /** @brief Log storage flush conditions.
+ * Map D-Bus interface name to condition description.
+ */
+ std::map<std::string, FlushCondition> conds_;
+};
diff --git a/src/log_manager.cpp b/src/log_manager.cpp
new file mode 100644
index 0000000..b78b7e8
--- /dev/null
+++ b/src/log_manager.cpp
@@ -0,0 +1,254 @@
+/**
+ * @brief Log manager.
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * 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 "config.hpp"
+#include "log_manager.hpp"
+
+#include <set>
+#include <vector>
+#include <cstring>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+// Path to the Unix Domain socket file used to read host's logs
+#define HOSTLOG_SOCKET_PATH "\0obmc-console"
+// Number of connection attempts
+#define HOSTLOG_SOCKET_ATTEMPTS 60
+// Pause between connection attempts in seconds
+#define HOSTLOG_SOCKET_PAUSE 1
+// Max buffer size to read from socket
+#define MAX_SOCKET_BUFFER_SIZE 512
+
+
+LogManager::LogManager()
+: fd_(-1)
+{
+}
+
+
+LogManager::~LogManager()
+{
+ closeHostLog();
+}
+
+
+int LogManager::openHostLog()
+{
+ int rc;
+
+ do {
+ // Create socket
+ fd_ = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd_ == -1) {
+ rc = errno;
+ fprintf(stderr, "Unable to create socket: error [%i] %s\n", rc, strerror(rc));
+ break;
+ }
+
+ // Set non-blocking mode for socket
+ int opt = 1;
+ rc = ioctl(fd_, FIONBIO, &opt);
+ if (rc != 0) {
+ rc = errno;
+ fprintf(stderr, "Unable to set non-blocking mode for log socket: error [%i] %s\n", rc, strerror(rc));
+ break;
+ }
+
+ sockaddr_un sa = { 0 };
+ sa.sun_family = AF_UNIX;
+ constexpr int min_path = sizeof(HOSTLOG_SOCKET_PATH) < sizeof(sa.sun_path) ?
+ sizeof(HOSTLOG_SOCKET_PATH) : sizeof(sa.sun_path);
+ memcpy(&sa.sun_path, HOSTLOG_SOCKET_PATH, min_path);
+
+ // Connect to host's log stream via socket.
+ // The owner of the socket (server) is obmc-console service and
+ // we have a dependency on it written in the systemd unit file, but
+ // we can't guarantee that the socket is initialized at the moment.
+ rc = -1;
+ for (int attempt = 0; rc != 0 && attempt < HOSTLOG_SOCKET_ATTEMPTS; ++attempt) {
+ rc = connect(fd_, reinterpret_cast<const sockaddr*>(&sa), sizeof(sa));
+ sleep(HOSTLOG_SOCKET_PAUSE);
+ }
+ if (rc < 0) {
+ rc = errno;
+ fprintf(stderr, "Unable to connect to host log socket: error [%i] %s\n", rc, strerror(rc));
+ }
+ }
+ while (false);
+
+ if (rc != 0)
+ closeHostLog();
+
+ return rc;
+}
+
+
+void LogManager::closeHostLog()
+{
+ if (fd_ != -1) {
+ ::close(fd_);
+ fd_ = -1;
+ }
+}
+
+
+int LogManager::getHostLogFd() const
+{
+ return fd_;
+}
+
+
+int LogManager::handleHostLog()
+{
+ int rc = 0;
+ std::vector<char> buff(MAX_SOCKET_BUFFER_SIZE);
+ size_t readLen = MAX_SOCKET_BUFFER_SIZE;
+
+ // Read all existing data from log stream
+ while (rc == 0 && readLen != 0) {
+ rc = readHostLog(&buff[0], buff.size(), readLen);
+ if (rc == 0 && readLen != 0)
+ storage_.parse(&buff[0], readLen);
+ }
+
+ return rc;
+}
+
+
+int LogManager::flush()
+{
+ int rc;
+
+ if (storage_.empty())
+ return 0; // Nothing to save
+
+ const std::string logFile = prepareLogPath();
+ if (logFile.empty())
+ return EIO;
+
+ rc = storage_.write(logFile.c_str());
+ if (rc != 0)
+ return rc;
+
+ storage_.clear();
+
+ // Non critical tasks, don't check returned status
+ rotateLogFiles();
+
+ return 0;
+}
+
+
+int LogManager::readHostLog(char* buffer, size_t bufferLen, size_t& readLen) const
+{
+ int rc = 0;
+
+ const ssize_t rsz = ::read(fd_, buffer, bufferLen);
+ if (rsz >= 0)
+ readLen = static_cast<size_t>(rsz);
+ else {
+ readLen = 0;
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ rc = errno;
+ fprintf(stderr, "Unable to read host log: error [%i] %s\n", rc, strerror(rc));
+ }
+ }
+
+ return rc;
+}
+
+
+std::string LogManager::prepareLogPath() const
+{
+ // Create path for logs
+ if (::access(loggerConfig.path, F_OK) != 0) {
+ const std::string logPath(loggerConfig.path);
+ const size_t len = logPath.length();
+ size_t pos = 0;
+ while (pos < len - 1) {
+ pos = logPath.find('/', pos + 1);
+ const std::string createPath = logPath.substr(0, pos);
+ if (::mkdir(createPath.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0 && errno != EEXIST) {
+ const int rc = errno;
+ fprintf(stderr, "Unable to create dir %s: error [%i] %s\n", createPath.c_str(), rc, strerror(rc));
+ return std::string();
+ }
+ }
+ }
+
+ // Construct log file name
+ time_t ts;
+ time(&ts);
+ tm lt = { 0 };
+ localtime_r(&ts, <);
+ char fileName[64];
+ snprintf(fileName, sizeof(fileName), "/host_%i%02i%02i_%02i%02i%02i.log.gz",
+ lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday,
+ lt.tm_hour, lt.tm_min, lt.tm_sec);
+
+ return std::string(loggerConfig.path) + fileName;
+}
+
+
+int LogManager::rotateLogFiles() const
+{
+ if (loggerConfig.rotationLimit == 0)
+ return 0; // Not applicable
+
+ int rc = 0;
+
+ // Get file list to std::set
+ std::set<std::string> logFiles;
+ DIR* dh = opendir(loggerConfig.path);
+ if (!dh) {
+ rc = errno;
+ fprintf(stderr, "Unable to open directory %s: error [%i] %s\n", loggerConfig.path, rc, strerror(rc));
+ return rc;
+ }
+ dirent *dir;
+ while ((dir = readdir(dh))) {
+ if (dir->d_type != DT_DIR)
+ logFiles.insert(dir->d_name);
+ }
+ closedir(dh);
+
+ // Log file has a name with a timestamp generated by prepareLogPath().
+ // The sorted array of names (std::set) will contain the oldest file on the
+ // top.
+ // Remove oldest files.
+ int filesToRemove = static_cast<int>(logFiles.size()) - loggerConfig.rotationLimit;
+ while (rc == 0 && --filesToRemove >= 0) {
+ std::string fileToRemove = loggerConfig.path;
+ fileToRemove += '/';
+ fileToRemove += *logFiles.begin();
+ if (::unlink(fileToRemove.c_str()) == -1) {
+ rc = errno;
+ fprintf(stderr, "Unable to delete file %s: error [%i] %s\n", fileToRemove.c_str(), rc, strerror(rc));
+ }
+ logFiles.erase(fileToRemove);
+ }
+
+ return rc;
+}
diff --git a/src/log_manager.hpp b/src/log_manager.hpp
new file mode 100644
index 0000000..5dac0f9
--- /dev/null
+++ b/src/log_manager.hpp
@@ -0,0 +1,106 @@
+/**
+ * @brief Log manager.
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include "log_storage.hpp"
+
+
+/** @class LogManager
+ * @brief Log manager.
+ * All functions within this class are not thread-safe.
+ */
+class LogManager
+{
+public:
+ /** @brief Constructor. */
+ LogManager();
+
+ /** @brief Destructor. */
+ ~LogManager();
+
+ /** @brief Open the host's log stream.
+ *
+ * @return error code, 0 if operation completed successfully
+ */
+ int openHostLog();
+
+ /** @brief Close the host's log stream.
+ */
+ void closeHostLog();
+
+ /** @brief Get file descriptor by host's log stream.
+ * Descriptor can be used to register it in an external polling manager.
+ *
+ * @return file descriptor (actually it is an opened socket)
+ */
+ int getHostLogFd() const;
+
+ /** @brief Handle incoming data from host's log stream.
+ *
+ * @return error code, 0 if operation completed successfully
+ */
+ int handleHostLog();
+
+ /** @brief Flush log storage: save currently collected messages to a file,
+ * reset the storage and rotate log files.
+ *
+ * @return error code, 0 if operation completed successfully
+ */
+ int flush();
+
+private:
+ /** @brief Read incoming data from host's log stream.
+ *
+ * @param[out] buffer - buffer to write incoming data
+ * @param[in] bufferLen - maximum size of the buffer in bytes
+ * @param[out] readLen - on output contain number of bytes read from stream
+ *
+ * @return error code, 0 if operation completed successfully
+ */
+ int readHostLog(char* buffer, size_t bufferLen, size_t& readLen) const;
+
+ /** @brief Prepare the path to save the log.
+ * Warning: the name is used in function rotateLogFiles(),
+ * make sure you don't brake sorting rules.
+ *
+ * @return path to new log file including its name
+ */
+ std::string prepareLogPath() const;
+
+ /** @brief Create path for log files.
+ *
+ * @return error code, 0 if operation completed successfully
+ */
+ int createLogPath() const;
+
+ /** @brief Rotate log files in the directory.
+ * Function remove oldest files and keep up to maxFiles_ log files.
+ *
+ * @return error code, 0 if operation completed successfully
+ */
+ int rotateLogFiles() const;
+
+private:
+ /** @brief Log storage. */
+ LogStorage storage_;
+ /** @brief File descriptor of the input log stream. */
+ int fd_;
+};
diff --git a/src/log_storage.cpp b/src/log_storage.cpp
new file mode 100644
index 0000000..1ada1c3
--- /dev/null
+++ b/src/log_storage.cpp
@@ -0,0 +1,173 @@
+/**
+ * @brief Log storage.
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * 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 "config.hpp"
+#include "log_storage.hpp"
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+
+
+LogStorage::LogStorage()
+: last_complete_(true)
+{
+}
+
+
+void LogStorage::parse(const char* data, size_t len)
+{
+ // Split log stream to separate messages.
+ // Stream may not be ended with EOL, so we handle this situation by
+ // last_complete_ flag.
+ size_t pos = 0;
+ while (pos < len) {
+ // Search for EOL ('\n')
+ size_t eol = pos;
+ while (eol < len && data[eol] != '\n')
+ ++eol;
+ const bool eolFound = eol < len;
+ const char* msgText = data + pos;
+ size_t msgLen = (eolFound ? eol : len) - pos;
+
+ // Remove '\r' from the end of message
+ while (msgLen && msgText[msgLen - 1] == '\r')
+ --msgLen;
+
+ // Append message to store
+ if (msgLen)
+ append(msgText, msgLen);
+
+ pos = eol + 1; // Skip '\n'
+ last_complete_ = eolFound;
+ }
+}
+
+
+void LogStorage::append(const char* msg, size_t len)
+{
+ if (!last_complete_) {
+ // The last message is incomplete, add msg as part of it
+ if (!messages_.empty()) {
+ Message& last_msg = *messages_.rbegin();
+ last_msg.text.append(msg, len);
+ }
+ }
+ else {
+ Message new_msg;
+ time(&new_msg.timeStamp);
+ new_msg.text.assign(msg, len);
+ messages_.push_back(new_msg);
+ shrink();
+ }
+}
+
+
+void LogStorage::clear()
+{
+ messages_.clear();
+ last_complete_ = true;
+}
+
+
+bool LogStorage::empty() const
+{
+ return messages_.empty();
+}
+
+
+int LogStorage::write(const char* fileName) const
+{
+ int rc = 0;
+
+ if (empty()) {
+ printf("No messages to write\n");
+ return 0;
+ }
+
+ const gzFile fd = gzopen(fileName, "w");
+ if (fd == Z_NULL) {
+ rc = errno;
+ fprintf(stderr, "Unable to open file %s: error [%i] %s\n",
+ fileName, rc, strerror(rc));
+ return rc;
+ }
+
+ // Write full datetime stamp as the first record
+ const time_t& logStartTime = messages_.begin()->timeStamp;
+ tm localTime = { 0 };
+ localtime_r(&logStartTime, &localTime);
+ char msgText[64];
+ snprintf(msgText, sizeof(msgText),
+ ">>> Log collection started at %02i.%02i.%i %02i:%02i:%02i",
+ localTime.tm_mday, localTime.tm_mon + 1, localTime.tm_year + 1900,
+ localTime.tm_hour, localTime.tm_min, localTime.tm_sec);
+ const Message startMsg = { logStartTime, msgText };
+ rc |= write(fd, startMsg);
+
+ // Write messages
+ for (auto it = messages_.begin(); rc == 0 && it != messages_.end(); ++it)
+ rc |= write(fd, *it);
+
+ rc = gzclose_w(fd);
+ if (rc != Z_OK)
+ fprintf(stderr, "Unable to close file %s: error [%i]\n", fileName, rc);
+
+ return rc;
+}
+
+
+int LogStorage::write(gzFile fd, const Message& msg) const
+{
+ // Convert timestamp to local time
+ tm localTime = { 0 };
+ localtime_r(&msg.timeStamp, &localTime);
+
+ // Write message to the file
+ const int rc = gzprintf(fd, "[ %02i:%02i:%02i ]: %s\n",
+ localTime.tm_hour,
+ localTime.tm_min,
+ localTime.tm_sec,
+ msg.text.c_str());
+ if (rc <= 0) {
+ fprintf(stderr, "Unable to write file: error [%i]\n", -rc);
+ return EIO;
+ }
+
+ return 0;
+}
+
+
+void LogStorage::shrink()
+{
+ if (loggerConfig.storageSizeLimit) {
+ while (messages_.size() > static_cast<size_t>(loggerConfig.storageSizeLimit))
+ messages_.pop_front();
+ }
+ if (loggerConfig.storageTimeLimit) {
+ // Get time for N hours ago
+ time_t oldestTimeStamp;
+ time(&oldestTimeStamp);
+ oldestTimeStamp -= loggerConfig.storageTimeLimit * 60 * 60;
+ while (!messages_.empty() && messages_.begin()->timeStamp < oldestTimeStamp)
+ messages_.pop_front();
+ }
+}
diff --git a/src/log_storage.hpp b/src/log_storage.hpp
new file mode 100644
index 0000000..b2a6271
--- /dev/null
+++ b/src/log_storage.hpp
@@ -0,0 +1,99 @@
+/**
+ * @brief Log storage.
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <ctime>
+#include <list>
+#include <string>
+#include <zlib.h>
+
+
+/** @class LogStorage
+ * @brief Log storage implementation.
+ * All functions within this class are not thread-safe.
+ */
+class LogStorage
+{
+public:
+ /** @brief Constructor. */
+ LogStorage();
+
+ /** @brief Parse input log stream and append messages to the storage.
+ *
+ * @param[in] data - pointer to the message buffer
+ * @param[in] len - length of the buffer in bytes
+ */
+ void parse(const char* data, size_t len);
+
+ /** @brief Clear (reset) storage. */
+ void clear();
+
+ /** @brief Check storage for empty.
+ *
+ * @return true if storage is empty
+ */
+ bool empty() const;
+
+ /** @brief Save messages from storage to the specified file.
+ *
+ * @param[in] fileName - path to the file
+ *
+ * @return error code, 0 if operation completed successfully
+ */
+ int write(const char* fileName) const;
+
+private:
+ /** @struct Message
+ * @brief Represent log message (single line from host log).
+ */
+ struct Message
+ {
+ /** @brief Timestamp (message creation time). */
+ time_t timeStamp;
+ /** @brief Text of the message. */
+ std::string text;
+ };
+
+ /** @brief Append new message to the storage.
+ *
+ * @param[in] msg - pointer to the message buffer
+ * @param[in] len - length of the buffer in bytes
+ */
+ void append(const char* msg, size_t len);
+
+ /** @brief Write message to the file.
+ *
+ * @param[in] fd - descriptor of the file to write
+ * @param[in] msg - message to write
+ *
+ * @return error code, 0 if operation completed successfully
+ */
+ int write(gzFile fd, const Message& msg) const;
+
+ /** @brief Shrink storage by removing oldest messages. */
+ void shrink();
+
+private:
+ /** @brief List of messages. */
+ std::list<Message> messages_;
+ /** @brief Flag to indicate that the last message is incomplete. */
+ bool last_complete_;
+};
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..7f58ef0
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,191 @@
+/**
+ * @brief Host logger service entry point.
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * 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 "config.hpp"
+#include "dbus_server.hpp"
+#include "dbus_watch.hpp"
+#include "log_manager.hpp"
+#include <getopt.h>
+#include <cstdio>
+#include <cstdlib>
+#include <climits>
+
+
+// Global logger configuration instance
+Config loggerConfig = {
+ .path = LOG_OUTPUT_PATH,
+ .storageSizeLimit = LOG_STORAGE_SIZE_LIMIT,
+ .storageTimeLimit = LOG_STORAGE_TIME_LIMIT,
+ .flushPeriod = LOG_FLUSH_PERIOD,
+ .rotationLimit = LOG_ROTATION_LIMIT
+};
+
+
+/** @brief Print title with version info. */
+static void printTitle()
+{
+ printf("Host logger service " PACKAGE_VERSION ".\n");
+}
+
+
+/** @brief Print help usage info.
+ *
+ * @param[in] app - application's file name
+ */
+static void printHelp(const char* app)
+{
+ printTitle();
+ printf("Copyright (c) 2018 YADRO.\n");
+ printf("Usage: %s [options]\n", app);
+ printf("Options (defaults are specified in brackets):\n"
+ " -p, --path=PATH Path used to store logs [%s]\n"
+ "Intermediate storage buffer capacity setup:\n"
+ " -s, --szlimit=N Store up to N last messages [%i], 0=unlimited\n"
+ " -t, --tmlimit=N Store messages for last N hours [%i], 0=unlimited\n"
+ "Flush storage buffer policy:\n"
+ " -f, --flush=N Flush logs every N hours [%i]\n"
+ " If this option is set to 0 flush will be called at\n"
+ " every host state change event from D-Bus.\n"
+ "Log files rotation policy:\n"
+ " -r, --rotate=N Store up to N files in the log directory [%i],\n"
+ " 0=unlimited\n"
+ "Common options:\n"
+ " -v, --version Print version and exit\n"
+ " -h, --help Print this help and exit\n",
+ loggerConfig.path,
+ loggerConfig.storageSizeLimit,
+ loggerConfig.storageTimeLimit,
+ loggerConfig.flushPeriod,
+ loggerConfig.rotationLimit);
+}
+
+
+/** @brief Get numeric positive value from string argument.
+ *
+ * @param[in] param - parameter name
+ * @param[in] arg - parameter text value
+ *
+ * @return positive numeric value from string argument or -1 on errors
+ */
+static int getNumericArg(const char* param, const char* arg)
+{
+ char* ep = nullptr;
+ const unsigned long val = strtoul(arg, &ep, 0);
+ if (val > INT_MAX || !ep || ep == arg || *ep != 0) {
+ fprintf(stderr, "Invalid %s param: %s, expected 0<=N<=%i\n",
+ param, arg, INT_MAX);
+ return -1;
+ }
+ return static_cast<int>(val);
+}
+
+
+/** @brief Application entry point. */
+int main(int argc, char *argv[])
+{
+ int opt_val;
+ const struct option opts[] = {
+ { "path", required_argument, 0, 'p' },
+ { "szlimit", required_argument, 0, 's' },
+ { "tmlimit", required_argument, 0, 't' },
+ { "flush", required_argument, 0, 'f' },
+ { "rotate", required_argument, 0, 'r' },
+ { "version", no_argument, 0, 'v' },
+ { "help", no_argument, 0, 'h' },
+ { 0, 0, 0, 0 }
+ };
+
+ opterr = 0;
+ while ((opt_val = getopt_long(argc, argv, "p:s:t:f:r:vh", opts, NULL)) != -1) {
+ switch (opt_val) {
+ case 'p':
+ loggerConfig.path = optarg;
+ if (*loggerConfig.path != '/') {
+ fprintf(stderr, "Invalid directory: %s, expected absolute path\n", loggerConfig.path);
+ return EXIT_FAILURE;
+ }
+ break;
+ case 's':
+ loggerConfig.storageSizeLimit = getNumericArg(opts[optind - 1].name, optarg);
+ if (loggerConfig.storageSizeLimit < 0)
+ return EXIT_FAILURE;
+ break;
+ case 't':
+ loggerConfig.storageTimeLimit = getNumericArg(opts[optind - 1].name, optarg);
+ if (loggerConfig.storageTimeLimit < 0)
+ return EXIT_FAILURE;
+ break;
+ case 'f':
+ loggerConfig.flushPeriod = getNumericArg(opts[optind - 1].name, optarg);
+ if (loggerConfig.flushPeriod < 0)
+ return EXIT_FAILURE;
+ break;
+ case 'r':
+ loggerConfig.rotationLimit = getNumericArg(opts[optind - 1].name, optarg);
+ if (loggerConfig.rotationLimit < 0)
+ return EXIT_FAILURE;
+ break;
+ case 'v':
+ printTitle();
+ return EXIT_SUCCESS;
+ case 'h':
+ printHelp(argv[0]);
+ return EXIT_SUCCESS;
+ default:
+ fprintf(stderr, "Invalid option: %s\n", argv[optind - 1]);
+ return EXIT_FAILURE;
+ }
+ }
+
+ int rc;
+
+ // Initialize log manager
+ LogManager logManager;
+ rc = logManager.openHostLog();
+ if (rc != 0)
+ return rc;
+
+ // Initialize D-Bus server
+ sdbusplus::bus::bus bus = sdbusplus::bus::new_default();
+ sd_event* event = nullptr;
+ rc = sd_event_default(&event);
+ if (rc < 0) {
+ fprintf(stderr, "Error occurred during the sd_event_default: %i\n", rc);
+ return EXIT_FAILURE;
+ }
+ EventPtr eventPtr(event);
+ bus.attach_event(eventPtr.get(), SD_EVENT_PRIORITY_NORMAL);
+
+ DbusServer dbusMgr(logManager, bus);
+ bus.request_name(HOSTLOGGER_DBUS_IFACE);
+
+ // Initialize D-Bus watcher
+ DbusWatcher dbusWatch(logManager, bus);
+ rc = dbusWatch.initialize();
+ if (rc < 0)
+ return EXIT_FAILURE;
+
+ // D-Bus event processing
+ rc = sd_event_loop(eventPtr.get());
+ if (rc != 0)
+ fprintf(stderr, "Error occurred during the sd_event_loop: %i\n", rc);
+
+ return rc ? rc : -1; // Allways retrun an error code
+}