Add multi-host support
This refactoring includes:
- added multi-host mode support;
- added support for graceful shutdown of the service;
- added support to flush the log buffer as it fills;
- D-Bus service xyz.openbmc_project.HostLogger replaced with SIGUSR1
signal handler;
- self diagnostic messages now registered via phosphor-logging;
- added unit tests;
- build system migrated from autotools to meson;
- source code aligned with OpenBMC conventions.
Change-Id: If6c1dfde278af685d8563450543a6587a282c7e4
Signed-off-by: Artem Senichev <a.senichev@yadro.com>
diff --git a/src/config.cpp b/src/config.cpp
new file mode 100644
index 0000000..d7f2ee1
--- /dev/null
+++ b/src/config.cpp
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
+
+#include "config.hpp"
+
+#include <algorithm>
+#include <climits>
+#include <cstring>
+#include <stdexcept>
+#include <string>
+
+/**
+ * @brief Set boolean value from environment variable.
+ *
+ * @param[in] name name of environment variable
+ * @param[out] value value to set
+ *
+ * @throw std::invalid_argument in case of errors
+ */
+static void safeSet(const char* name, bool& value)
+{
+ const char* envVal = std::getenv(name);
+ if (envVal)
+ {
+ if (strcmp(envVal, "true") == 0)
+ {
+ value = true;
+ }
+ else if (strcmp(envVal, "false") == 0)
+ {
+ value = false;
+ }
+ else
+ {
+ std::string err = "Invalid value of environment variable ";
+ err += name;
+ err += ": '";
+ err += envVal;
+ err += "', expected 'true' or 'false'";
+ throw std::invalid_argument(err);
+ }
+ }
+}
+
+/**
+ * @brief Set unsigned numeric value from environment variable.
+ *
+ * @param[in] name name of environment variable
+ * @param[out] value value to set
+ *
+ * @throw std::invalid_argument in case of errors
+ */
+static void safeSet(const char* name, size_t& value)
+{
+ const char* envVal = std::getenv(name);
+ if (envVal)
+ {
+ const size_t num = strtoul(envVal, nullptr, 0);
+ if (std::all_of(envVal, envVal + strlen(envVal), isdigit) &&
+ num != ULONG_MAX)
+ {
+ value = num;
+ }
+ else
+ {
+ std::string err = "Invalid argument: ";
+ err += envVal;
+ err += ", expected unsigned numeric value";
+ throw std::invalid_argument(err);
+ }
+ }
+}
+
+/**
+ * @brief Set string value from environment variable.
+ *
+ * @param[in] name name of environment variable
+ * @param[out] value value to set
+ */
+static void safeSet(const char* name, const char*& value)
+{
+ const char* envVal = std::getenv(name);
+ if (envVal)
+ {
+ value = envVal;
+ }
+}
+
+Config::Config()
+{
+ safeSet("SOCKET_ID", socketId);
+ safeSet("BUF_MAXSIZE", bufMaxSize);
+ safeSet("BUF_MAXTIME", bufMaxTime);
+ safeSet("FLUSH_FULL", bufFlushFull);
+ safeSet("HOST_STATE", hostState);
+ safeSet("OUT_DIR", outDir);
+ safeSet("MAX_FILES", maxFiles);
+
+ // Validate parameters
+ if (bufFlushFull && !bufMaxSize && !bufMaxTime)
+ {
+ throw std::invalid_argument(
+ "Flush policy is set to save the buffer as it fills, but buffer's "
+ "limits are not defined");
+ }
+}
diff --git a/src/config.hpp b/src/config.hpp
index 5264a5e..a2aa614 100644
--- a/src/config.hpp
+++ b/src/config.hpp
@@ -1,41 +1,35 @@
-/**
- * @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.
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
#pragma once
-/** @struct Config
- * @brief Represent the configuration of the logger.
+#include <cstddef>
+
+/**
+ * @struct Config
+ * @brief Configuration of the service, initialized with default values.
*/
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 Constructor: load configuration from environment variables.
+ *
+ * @throw std::invalid_argument invalid format in one of the variables
+ */
+ Config();
-/** @brief Global logger configuration instance. */
-extern Config loggerConfig;
+ /** @brief Socket ID used for connection with host console. */
+ const char* socketId = "";
+ /** @brief Max number of messages stored inside intermediate buffer. */
+ size_t bufMaxSize = 3000;
+ /** @brief Max age of messages (in minutes) inside intermediate buffer. */
+ size_t bufMaxTime = 0;
+ /** @brief Flag indicated we need to flush console buffer as it fills. */
+ bool bufFlushFull = false;
+ /** @brief Path to D-Bus object that provides host's state information. */
+ const char* hostState = "/xyz/openbmc_project/state/host0";
+ /** @brief Absolute path to the output directory for log files. */
+ const char* outDir = "/var/lib/obmc/hostlogs";
+ /** @brief Max number of log files in the output directory. */
+ size_t maxFiles = 10;
+};
diff --git a/src/dbus_loop.cpp b/src/dbus_loop.cpp
new file mode 100644
index 0000000..e628d24
--- /dev/null
+++ b/src/dbus_loop.cpp
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
+
+#include "dbus_loop.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+#include <system_error>
+
+using namespace phosphor::logging;
+
+DbusLoop::DbusLoop() : bus(nullptr), event(nullptr)
+{
+ int rc;
+
+ rc = sd_bus_default(&bus);
+ if (rc < 0)
+ {
+ std::error_code ec(-rc, std::generic_category());
+ throw std::system_error(ec, "Unable to initiate D-Bus connection");
+ }
+
+ rc = sd_event_default(&event);
+ if (rc < 0)
+ {
+ sd_bus_unref(bus);
+ std::error_code ec(-rc, std::generic_category());
+ throw std::system_error(ec, "Unable to create D-Bus event loop");
+ }
+
+ rc = sd_bus_attach_event(bus, event, SD_EVENT_PRIORITY_NORMAL);
+ if (rc < 0)
+ {
+ sd_bus_unref(bus);
+ sd_event_unref(event);
+ std::error_code ec(-rc, std::generic_category());
+ throw std::system_error(ec, "Unable to attach D-Bus event");
+ }
+}
+
+DbusLoop::~DbusLoop()
+{
+ sd_bus_unref(bus);
+ sd_event_unref(event);
+}
+
+int DbusLoop::run() const
+{
+ return sd_event_loop(event);
+}
+
+void DbusLoop::stop(int code) const
+{
+ sd_event_exit(event, code);
+}
+
+void DbusLoop::addPropertyHandler(const std::string& objPath,
+ const WatchProperties& props,
+ std::function<void()> callback)
+{
+ // Add match handler
+ const int rc = sd_bus_match_signal(bus, nullptr, nullptr, objPath.c_str(),
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged", msgCallback, this);
+ if (rc < 0)
+ {
+ std::error_code ec(-rc, std::generic_category());
+ throw std::system_error(ec, "Unable to register property watcher");
+ }
+
+ propWatch = props;
+ propHandler = callback;
+}
+
+void DbusLoop::addIoHandler(int fd, std::function<void()> callback)
+{
+ ioHandler = callback;
+ const int rc = sd_event_add_io(event, nullptr, fd, EPOLLIN,
+ &DbusLoop::ioCallback, this);
+ if (rc < 0)
+ {
+ std::error_code ec(-rc, std::generic_category());
+ throw std::system_error(ec, "Unable to register IO handler");
+ }
+}
+
+void DbusLoop::addSignalHandler(int signal, std::function<void()> callback)
+{
+ // Block the signal
+ sigset_t ss;
+ if (sigemptyset(&ss) < 0 || sigaddset(&ss, signal) < 0 ||
+ sigprocmask(SIG_BLOCK, &ss, nullptr) < 0)
+ {
+ std::error_code ec(errno, std::generic_category());
+ std::string err = "Unable to block signal ";
+ err += strsignal(signal);
+ throw std::system_error(ec, err);
+ }
+
+ signalHandlers.insert(std::make_pair(signal, callback));
+
+ // Register handler
+ const int rc = sd_event_add_signal(event, nullptr, signal,
+ &DbusLoop::signalCallback, this);
+ if (rc < 0)
+ {
+ std::error_code ec(-rc, std::generic_category());
+ std::string err = "Unable to register handler for signal ";
+ err += strsignal(signal);
+ throw std::system_error(ec, err);
+ }
+}
+
+int DbusLoop::msgCallback(sd_bus_message* msg, void* userdata,
+ sd_bus_error* /*err*/)
+{
+ const WatchProperties& propWatch =
+ static_cast<DbusLoop*>(userdata)->propWatch;
+
+ try
+ {
+ int rc;
+
+ // Filter out by interface name
+ const char* interface;
+ rc = sd_bus_message_read(msg, "s", &interface);
+ if (rc < 0)
+ {
+ std::error_code ec(-rc, std::generic_category());
+ throw std::system_error(ec, "Unable to read interface name");
+ }
+ const auto& itIface = propWatch.find(interface);
+ if (itIface == propWatch.end())
+ {
+ return 0; // Interface is now watched
+ }
+ const Properties& props = itIface->second;
+
+ // Read message: go through list of changed properties
+ rc = sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY, "{sv}");
+ if (rc < 0)
+ {
+ std::error_code ec(-rc, std::generic_category());
+ throw std::system_error(ec, "Unable to open message container");
+ }
+ while ((rc = sd_bus_message_enter_container(msg, SD_BUS_TYPE_DICT_ENTRY,
+ "sv")) > 0)
+ {
+ // Get property's name
+ const char* name;
+ rc = sd_bus_message_read(msg, "s", &name);
+ if (rc < 0)
+ {
+ sd_bus_message_exit_container(msg);
+ std::error_code ec(-rc, std::generic_category());
+ throw std::system_error(ec, "Unable to get property name");
+ }
+
+ // Get and check property's type
+ const char* type;
+ rc = sd_bus_message_peek_type(msg, nullptr, &type);
+ if (rc < 0 || strcmp(type, "s"))
+ {
+ sd_bus_message_exit_container(msg);
+ continue;
+ }
+
+ // Get property's value
+ const char* value;
+ rc = sd_bus_message_enter_container(msg, SD_BUS_TYPE_VARIANT, type);
+ if (rc < 0)
+ {
+ sd_bus_message_exit_container(msg);
+ std::error_code ec(-rc, std::generic_category());
+ throw std::system_error(ec, "Unable to open property value");
+ }
+ rc = sd_bus_message_read(msg, type, &value);
+ if (rc < 0)
+ {
+ sd_bus_message_exit_container(msg);
+ sd_bus_message_exit_container(msg);
+ std::error_code ec(-rc, std::generic_category());
+ throw std::system_error(ec, "Unable to get property value");
+ }
+ sd_bus_message_exit_container(msg);
+
+ // Check property name/value and handle the match
+ const auto& itProps = props.find(name);
+ if (itProps != props.end() &&
+ itProps->second.find(value) != itProps->second.end())
+ {
+ static_cast<DbusLoop*>(userdata)->propHandler();
+ }
+
+ sd_bus_message_exit_container(msg);
+ }
+ sd_bus_message_exit_container(msg);
+ }
+ catch (const std::exception& ex)
+ {
+ log<level::WARNING>(ex.what());
+ }
+
+ return 0;
+}
+
+int DbusLoop::signalCallback(sd_event_source* /*src*/,
+ const struct signalfd_siginfo* si, void* userdata)
+{
+ DbusLoop* instance = static_cast<DbusLoop*>(userdata);
+ const auto it = instance->signalHandlers.find(si->ssi_signo);
+ if (it != instance->signalHandlers.end())
+ {
+ it->second();
+ }
+ else
+ {
+ std::string msg = "Unhandled signal ";
+ msg += strsignal(si->ssi_signo);
+ log<level::WARNING>(msg.c_str());
+ }
+ return 0;
+}
+
+int DbusLoop::ioCallback(sd_event_source* /*src*/, int /*fd*/,
+ uint32_t /*revents*/, void* userdata)
+{
+ static_cast<DbusLoop*>(userdata)->ioHandler();
+ return 0;
+}
diff --git a/src/dbus_loop.hpp b/src/dbus_loop.hpp
new file mode 100644
index 0000000..eeb5327
--- /dev/null
+++ b/src/dbus_loop.hpp
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
+
+#pragma once
+
+#include <systemd/sd-bus.h>
+
+#include <functional>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+/**
+ * @class DbusLoop
+ * @brief D-Bus based event loop.
+ */
+class DbusLoop
+{
+ public:
+ /** @brief Set of possible values of the property. */
+ using PropertyValues = std::set<std::string>;
+ /** @brief Map of properties: name -> watched values. */
+ using Properties = std::map<std::string, PropertyValues>;
+ /** @brief Map of watched properties: interface -> properties. */
+ using WatchProperties = std::map<std::string, Properties>;
+
+ DbusLoop();
+ ~DbusLoop();
+
+ /**
+ * @brief Run worker loop.
+ *
+ * @return exit code from loop
+ */
+ int run() const;
+
+ /**
+ * @brief Stop worker loop.
+ *
+ * @param[in] code exit code
+ */
+ void stop(int code) const;
+
+ /**
+ * @brief Add property change handler.
+ *
+ * @param[in] service D-Bus service name (object owner)
+ * @param[in] objPath path to the D-Bus object
+ * @param[in] props watched properties description
+ * @param[in] callback function to call when property get one the listed
+ * values
+ *
+ * @throw std::system_error in case of errors
+ */
+ void addPropertyHandler(const std::string& objPath,
+ const WatchProperties& props,
+ std::function<void()> callback);
+
+ /**
+ * @brief Add IO event handler.
+ *
+ * @param[in] fd file descriptor to watch
+ * @param[in] callback function to call when IO event is occurred
+ *
+ * @throw std::system_error in case of errors
+ */
+ void addIoHandler(int fd, std::function<void()> callback);
+
+ /**
+ * @brief Add signal handler.
+ *
+ * @param[in] signal signal to watch
+ * @param[in] callback function to call when signal is triggered
+ *
+ * @throw std::system_error in case of errors
+ */
+ void addSignalHandler(int signal, std::function<void()> callback);
+
+ private:
+ /**
+ * @brief D-Bus callback: message handler.
+ * See sd_bus_message_handler_t for details.
+ */
+ static int msgCallback(sd_bus_message* msg, void* userdata,
+ sd_bus_error* err);
+
+ /**
+ * @brief D-Bus callback: signal handler.
+ * See sd_event_signal_handler_t for details.
+ */
+ static int signalCallback(sd_event_source* src,
+ const struct signalfd_siginfo* si,
+ void* userdata);
+
+ /**
+ * @brief D-Bus callback: IO handler.
+ * See sd_event_io_handler_t for details.
+ */
+ static int ioCallback(sd_event_source* src, int fd, uint32_t revents,
+ void* userdata);
+
+ private:
+ /** @brief D-Bus connection. */
+ sd_bus* bus;
+ /** @brief D-Bus event loop. */
+ sd_event* event;
+
+ /** @brief Watched properties. */
+ WatchProperties propWatch;
+ /** @brief Property change handler. */
+ std::function<void()> propHandler;
+
+ /** @brief IO handler. */
+ std::function<void()> ioHandler;
+
+ /** @brief Signal handlers. */
+ std::map<int, std::function<void()>> signalHandlers;
+};
diff --git a/src/dbus_server.cpp b/src/dbus_server.cpp
deleted file mode 100644
index aba7694..0000000
--- a/src/dbus_server.cpp
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * @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
deleted file mode 100644
index e058edf..0000000
--- a/src/dbus_server.hpp
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * @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
deleted file mode 100644
index ed57b87..0000000
--- a/src/dbus_watch.cpp
+++ /dev/null
@@ -1,165 +0,0 @@
-/**
- * @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 "dbus_watch.hpp"
-
-#include "config.hpp"
-
-#include <chrono>
-#include <set>
-#include <string>
-
-// 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, std::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 = std::get<std::string>(itp->second);
- 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
deleted file mode 100644
index 6a284b4..0000000
--- a/src/dbus_watch.hpp
+++ /dev/null
@@ -1,110 +0,0 @@
-/**
- * @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 <map>
-#include <sdbusplus/bus/match.hpp>
-#include <set>
-#include <string>
-
-/** @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/file_storage.cpp b/src/file_storage.cpp
new file mode 100644
index 0000000..ace7a1b
--- /dev/null
+++ b/src/file_storage.cpp
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
+
+#include "file_storage.hpp"
+
+#include "zlib_file.hpp"
+
+#include <set>
+
+namespace fs = std::filesystem;
+
+/** @brief File extension for log files. */
+static const std::string fileExt = ".log.gz";
+
+FileStorage::FileStorage(const std::string& path, const std::string& prefix,
+ size_t maxFiles) :
+ outDir(path),
+ filePrefix(prefix), filesLimit(maxFiles)
+{
+ // Check path
+ if (!outDir.is_absolute())
+ {
+ throw std::invalid_argument("Path must be absolute");
+ }
+ fs::create_directories(outDir);
+
+ // Normalize file name prefix
+ if (filePrefix.empty())
+ {
+ filePrefix = "host";
+ }
+}
+
+std::string FileStorage::save(const LogBuffer& buf) const
+{
+ if (buf.empty())
+ {
+ return std::string(); // Buffer is empty, nothing to save
+ }
+
+ const std::string fileName = newFile();
+ ZlibFile logFile(fileName);
+
+ // Write full datetime stamp as the first record
+ tm tmLocal;
+ localtime_r(&buf.begin()->timeStamp, &tmLocal);
+ char tmText[20]; // asciiz for YYYY-MM-DD HH:MM:SS
+ strftime(tmText, sizeof(tmText), "%F %T", &tmLocal);
+ std::string titleMsg = ">>> Log collection started at ";
+ titleMsg += tmText;
+ logFile.write(tmLocal, titleMsg);
+
+ // Write messages
+ for (const auto& msg : buf)
+ {
+ localtime_r(&msg.timeStamp, &tmLocal);
+ logFile.write(tmLocal, msg.text);
+ }
+
+ logFile.close();
+
+ rotate();
+
+ return fileName;
+}
+
+std::string FileStorage::newFile() const
+{
+ // Prepare directory
+ fs::create_directories(outDir);
+
+ // Construct log file name: {prefix}_{timestamp}[_N].{ext}
+ std::string fileName = outDir / (filePrefix + '_');
+
+ time_t tmCurrent;
+ time(&tmCurrent);
+ tm tmLocal;
+ localtime_r(&tmCurrent, &tmLocal);
+ char tmText[16]; // asciiz for YYYYMMDD_HHMMSS
+ strftime(tmText, sizeof(tmText), "%Y%m%d_%H%M%S", &tmLocal);
+ fileName += tmText;
+
+ // Handle duplicate files
+ std::string dupPostfix;
+ size_t dupCounter = 0;
+ while (fs::exists(fileName + dupPostfix + fileExt))
+ {
+ dupPostfix = '_' + std::to_string(++dupCounter);
+ }
+ fileName += dupPostfix;
+ fileName += fileExt;
+
+ return fileName;
+}
+
+void FileStorage::rotate() const
+{
+ if (!filesLimit)
+ {
+ return; // Unlimited
+ }
+
+ // Get file list to ordered set
+ std::set<std::string> logFiles;
+ for (const auto& file : fs::directory_iterator(outDir))
+ {
+ if (!fs::is_regular_file(file))
+ {
+ continue;
+ }
+ const std::string fileName = file.path().filename();
+
+ const size_t minFileNameLen = filePrefix.length() +
+ 15 + // time stamp YYYYMMDD_HHMMSS
+ fileExt.length();
+ if (fileName.length() < minFileNameLen)
+ {
+ continue;
+ }
+
+ if (fileName.compare(fileName.length() - fileExt.length(),
+ fileExt.length(), fileExt))
+ {
+ continue;
+ }
+
+ const std::string fullPrefix = filePrefix + '_';
+ if (fileName.compare(0, fullPrefix.length(), fullPrefix))
+ {
+ continue;
+ }
+
+ logFiles.insert(fileName);
+ }
+
+ // Log file has a name with a timestamp generated. The sorted set contains
+ // the oldest file on the top, remove them.
+ if (logFiles.size() > filesLimit)
+ {
+ size_t removeCount = logFiles.size() - filesLimit;
+ for (const auto& fileName : logFiles)
+ {
+ fs::remove(outDir / fileName);
+ if (!--removeCount)
+ {
+ break;
+ }
+ }
+ }
+}
diff --git a/src/file_storage.hpp b/src/file_storage.hpp
new file mode 100644
index 0000000..0292440
--- /dev/null
+++ b/src/file_storage.hpp
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
+
+#pragma once
+
+#include "log_buffer.hpp"
+
+#include <filesystem>
+
+/**
+ * @class FileStorage
+ * @brief Persistent file storage with automatic log file rotation.
+ */
+class FileStorage
+{
+ public:
+ /**
+ * @brief Constructor.
+ *
+ * @param[in] path absolute path to the output directory
+ * @param[in] prefix prefix used for log file names
+ * @param[in] maxFiles max number of log files that can be stored
+ *
+ * @throw std::exception in case of errors
+ */
+ FileStorage(const std::string& path, const std::string& prefix,
+ size_t maxFiles);
+
+ /**
+ * @brief Save log buffer to a file.
+ *
+ * @param[in] buf buffer with log message to save
+ *
+ * @throw std::exception in case of errors
+ *
+ * @return path to saved file
+ */
+ std::string save(const LogBuffer& buf) const;
+
+ private:
+ /**
+ * @brief Prepare output directory for a new log file and construct path.
+ *
+ * @throw std::exception in case of errors
+ *
+ * @return full path to the new file
+ */
+ std::string newFile() const;
+
+ /**
+ * @brief Rotate log files in the output directory by removing the oldest
+ * logs.
+ *
+ * @throw std::exception in case of errors
+ */
+ void rotate() const;
+
+ private:
+ /** @brief Output directory. */
+ std::filesystem::path outDir;
+ /** @brief Prefix used for log file names. */
+ std::string filePrefix;
+ /** @brief Max number of log files that can be stored. */
+ size_t filesLimit;
+};
diff --git a/src/host_console.cpp b/src/host_console.cpp
new file mode 100644
index 0000000..bddc08e
--- /dev/null
+++ b/src/host_console.cpp
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
+
+#include "host_console.hpp"
+
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <cstring>
+#include <system_error>
+
+/**
+ * @brief Base path to the console's socket.
+ * See obmc-console for details.
+ */
+static constexpr char socketPath[] = "\0obmc-console";
+
+HostConsole::HostConsole(const std::string& socketId) :
+ socketId(socketId), socketFd(-1)
+{}
+
+HostConsole::~HostConsole()
+{
+ if (socketFd != -1)
+ {
+ close(socketFd);
+ }
+}
+
+void HostConsole::connect()
+{
+ if (socketFd != -1)
+ {
+ throw std::runtime_error("Socket already opened");
+ }
+
+ // Create socket
+ socketFd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (socketFd == -1)
+ {
+ std::error_code ec(errno ? errno : EIO, std::generic_category());
+ throw std::system_error(ec, "Unable to create socket");
+ }
+
+ // Set non-blocking mode for socket
+ int opt = 1;
+ if (ioctl(socketFd, FIONBIO, &opt))
+ {
+ std::error_code ec(errno ? errno : EIO, std::generic_category());
+ throw std::system_error(ec, "Unable to set non-blocking mode");
+ }
+
+ // Construct path to the socket file (see obmc-console for details)
+ std::string path(socketPath, socketPath + sizeof(socketPath) - 1);
+ if (!socketId.empty())
+ {
+ path += '.';
+ path += socketId;
+ }
+ if (path.length() > sizeof(sockaddr_un::sun_path))
+ {
+ throw std::invalid_argument("Invalid socket ID");
+ }
+
+ sockaddr_un sa;
+ sa.sun_family = AF_UNIX;
+ memcpy(&sa.sun_path, path.c_str(), path.length());
+
+ // 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.
+ size_t connectAttempts = 60; // Number of attempts
+ const socklen_t len = sizeof(sa) - sizeof(sa.sun_path) + path.length();
+ int rc = -1;
+ while (connectAttempts--)
+ {
+ rc = ::connect(socketFd, reinterpret_cast<const sockaddr*>(&sa), len);
+ if (!rc)
+ {
+ break;
+ }
+ else
+ {
+ sleep(1); // Make 1 second pause between attempts
+ }
+ }
+ if (rc)
+ {
+ std::string err = "Unable to connect to console";
+ if (!socketId.empty())
+ {
+ err += ' ';
+ err += socketId;
+ }
+ std::error_code ec(errno ? errno : EIO, std::generic_category());
+ throw std::system_error(ec, err);
+ }
+}
+
+size_t HostConsole::read(char* buf, size_t sz) const
+{
+ ssize_t rsz = ::read(socketFd, buf, sz);
+ if (rsz < 0)
+ {
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ {
+ // We are in non-blocking mode, so ignore these codes
+ rsz = 0;
+ }
+ else
+ {
+ std::string err = "Unable to read socket";
+ if (!socketId.empty())
+ {
+ err += ' ';
+ err += socketId;
+ }
+ std::error_code ec(errno ? errno : EIO, std::generic_category());
+ throw std::system_error(ec, err);
+ }
+ }
+
+ return static_cast<size_t>(rsz);
+}
+
+HostConsole::operator int() const
+{
+ return socketFd;
+}
diff --git a/src/host_console.hpp b/src/host_console.hpp
new file mode 100644
index 0000000..b0369af
--- /dev/null
+++ b/src/host_console.hpp
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
+
+#pragma once
+
+#include <string>
+
+/**
+ * @class HostConsole
+ * @brief Connection with host's console.
+ */
+class HostConsole
+{
+ public:
+ /**
+ * @brief Constructor.
+ *
+ * @param[in] socketId socket ID used for construction path to the socket
+ */
+ HostConsole(const std::string& socketId);
+
+ ~HostConsole();
+
+ /**
+ * @brief Connect to the host's console via socket.
+ *
+ * @throw std::invalid_argument if socket ID is invalid
+ * @throw std::system_error in case of other errors
+ */
+ void connect();
+
+ /**
+ * @brief Non-blocking read data from console's socket.
+ *
+ * @param[out] buf buffer to write the incoming data
+ * @param[in] sz size of the buffer
+ *
+ * @throw std::system_error in case of errors
+ *
+ * @return number of actually read bytes
+ */
+ size_t read(char* buf, size_t sz) const;
+
+ /** @brief Get socket file descriptor, used for watching IO. */
+ operator int() const;
+
+ private:
+ /** @brief Socket Id. */
+ std::string socketId;
+ /** @brief File descriptor of the socket. */
+ int socketFd;
+};
diff --git a/src/log_buffer.cpp b/src/log_buffer.cpp
new file mode 100644
index 0000000..f511a95
--- /dev/null
+++ b/src/log_buffer.cpp
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
+
+#include "log_buffer.hpp"
+
+/** @brief Check if a character is EOL symbol. */
+constexpr bool isEol(char c)
+{
+ return c == '\r' || c == '\n';
+}
+
+LogBuffer::LogBuffer(size_t maxSize, size_t maxTime) :
+ lastComplete(true), sizeLimit(maxSize), timeLimit(maxTime)
+{}
+
+void LogBuffer::append(const char* data, size_t sz)
+{
+ // Split raw data into separate messages by EOL symbols (\r or \n).
+ // Stream may not be ended with EOL, so we handle this situation by
+ // lastComplete flag.
+ size_t pos = 0;
+ while (pos < sz)
+ {
+ // Search for EOL ('\r' or '\n')
+ size_t eol = pos;
+ while (eol < sz)
+ {
+ if (isEol(data[eol]))
+ {
+ break;
+ }
+ ++eol;
+ }
+ const bool eolFound = eol < sz;
+ const char* msgText = data + pos;
+ const size_t msgLen = (eolFound ? eol : sz) - pos;
+
+ // Append message to the container
+ if (!lastComplete && !messages.empty())
+ {
+ // The last message is incomplete, add data as part of it
+ messages.back().text.append(msgText, msgLen);
+ }
+ else
+ {
+ Message msg;
+ time(&msg.timeStamp);
+ msg.text.assign(msgText, msgLen);
+ messages.push_back(msg);
+ }
+ lastComplete = eolFound;
+
+ // Move current position and skip EOL character
+ pos = eol + 1;
+ // Handle EOL sequences '\r\n' or '\n\r' as one delimiter
+ if (eolFound && pos < sz && isEol(data[pos]) && data[eol] != data[pos])
+ {
+ ++pos;
+ }
+ }
+
+ shrink();
+}
+
+void LogBuffer::setFullHandler(std::function<void()> cb)
+{
+ fullHandler = cb;
+}
+
+void LogBuffer::clear()
+{
+ messages.clear();
+ lastComplete = true;
+}
+
+bool LogBuffer::empty() const
+{
+ return messages.empty();
+}
+
+LogBuffer::container_t::const_iterator LogBuffer::begin() const
+{
+ return messages.begin();
+}
+
+LogBuffer::container_t::const_iterator LogBuffer::end() const
+{
+ return messages.end();
+}
+
+void LogBuffer::shrink()
+{
+ if (sizeLimit && messages.size() > sizeLimit)
+ {
+ if (fullHandler)
+ {
+ fullHandler();
+ }
+ while (messages.size() > sizeLimit)
+ {
+ messages.pop_front();
+ }
+ }
+ if (timeLimit && !messages.empty())
+ {
+ time_t oldest;
+ time(&oldest);
+ oldest -= timeLimit * 60 /* sec */;
+ if (messages.begin()->timeStamp < oldest)
+ {
+ if (fullHandler)
+ {
+ fullHandler();
+ }
+ while (!messages.empty() && messages.begin()->timeStamp < oldest)
+ {
+ messages.pop_front();
+ }
+ }
+ }
+}
diff --git a/src/log_buffer.hpp b/src/log_buffer.hpp
new file mode 100644
index 0000000..24e7ad7
--- /dev/null
+++ b/src/log_buffer.hpp
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
+
+#pragma once
+
+#include <ctime>
+#include <functional>
+#include <list>
+#include <string>
+
+/**
+ * @class LogBuffer
+ * @brief Container with automatic log message rotation.
+ */
+class LogBuffer
+{
+ public:
+ /**
+ * @struct Message
+ * @brief Single log message.
+ */
+ struct Message
+ {
+ /** @brief Message creation time. */
+ time_t timeStamp;
+ /** @brief Text of the message. */
+ std::string text;
+ };
+
+ using container_t = std::list<Message>;
+
+ /**
+ * @brief Constructor.
+ *
+ * @param[in] maxSize max number of messages that can be stored
+ * @param[in] maxTime max age of messages that can be stored, in minutes
+ */
+ LogBuffer(size_t maxSize, size_t maxTime);
+
+ /**
+ * @brief Add raw data from host's console output.
+ *
+ * @param[in] data pointer to raw data buffer
+ * @param[in] sz size of the buffer in bytes
+ */
+ void append(const char* data, size_t sz);
+
+ /**
+ * @brief Set handler called if buffer is full.
+ *
+ * @param[in] cb callback function
+ */
+ void setFullHandler(std::function<void()> cb);
+
+ /** @brief Clear (reset) container. */
+ void clear();
+ /** @brief Check container for empty. */
+ bool empty() const;
+ /** @brief Get container's iterator. */
+ container_t::const_iterator begin() const;
+ /** @brief Get container's iterator. */
+ container_t::const_iterator end() const;
+
+ private:
+ /** @brief Remove the oldest messages from container. */
+ void shrink();
+
+ private:
+ /** @brief Log message list. */
+ container_t messages;
+ /** @brief Flag to indicate that the last message is incomplete. */
+ bool lastComplete;
+ /** @brief Max number of messages that can be stored. */
+ size_t sizeLimit;
+ /** @brief Max age of messages (in minutes) that can be stored. */
+ size_t timeLimit;
+ /** @brief Callback function called if buffer is full. */
+ std::function<void()> fullHandler;
+};
diff --git a/src/log_file.cpp b/src/log_file.cpp
deleted file mode 100644
index 0f7a20a..0000000
--- a/src/log_file.cpp
+++ /dev/null
@@ -1,76 +0,0 @@
-/**
- * @brief Log file.
- *
- * This file is part of HostLogger project.
- *
- * Copyright (c) 2020 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 "log_file.hpp"
-
-#include "zlib_exception.hpp"
-
-LogFile::LogFile(const char* fileName)
-{
- fd_ = gzopen(fileName, "w");
- if (fd_ == Z_NULL)
- throw ZlibException(ZlibException::Create, Z_ERRNO, fd_, fileName);
- fileName_ = fileName;
-}
-
-LogFile::~LogFile()
-{
- if (fd_ != Z_NULL)
- gzclose_w(fd_);
-}
-
-void LogFile::close()
-{
- if (fd_ != Z_NULL)
- {
- const int rc = gzclose_w(fd_);
- if (rc != Z_OK)
- throw ZlibException(ZlibException::Close, rc, fd_, fileName_);
- fd_ = Z_NULL;
- fileName_.clear();
- }
-}
-
-void LogFile::write(time_t timeStamp, const std::string& message) const
-{
- int rc;
-
- // Convert time stamp and write it
- tm tmLocal;
- localtime_r(&timeStamp, &tmLocal);
- rc = gzprintf(fd_, "[ %02i:%02i:%02i ]: ", tmLocal.tm_hour, tmLocal.tm_min,
- tmLocal.tm_sec);
- if (rc <= 0)
- throw ZlibException(ZlibException::Write, rc, fd_, fileName_);
-
- // Write message
- const size_t len = message.length();
- if (len)
- {
- rc = gzwrite(fd_, message.data(), static_cast<unsigned int>(len));
- if (rc <= 0)
- throw ZlibException(ZlibException::Write, rc, fd_, fileName_);
- }
-
- // Write EOL
- rc = gzputc(fd_, '\n');
- if (rc <= 0)
- throw ZlibException(ZlibException::Write, rc, fd_, fileName_);
-}
diff --git a/src/log_file.hpp b/src/log_file.hpp
deleted file mode 100644
index 8494f8b..0000000
--- a/src/log_file.hpp
+++ /dev/null
@@ -1,67 +0,0 @@
-/**
- * @brief Log file.
- *
- * This file is part of HostLogger project.
- *
- * Copyright (c) 2020 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 <zlib.h>
-
-#include <ctime>
-#include <string>
-
-/** @class LogFile
- * @brief Log file writer.
- */
-class LogFile
-{
- public:
- /** @brief Constructor - open new file for writing logs.
- *
- * @param[in] fileName - path to the file
- *
- * @throw ZlibException in case of errors
- */
- LogFile(const char* fileName);
-
- ~LogFile();
-
- LogFile(const LogFile&) = delete;
- LogFile& operator=(const LogFile&) = delete;
-
- /** @brief Close file.
- *
- * @throw ZlibException in case of errors
- */
- void close();
-
- /** @brief Write log message to file.
- *
- * @param[in] timeStamp - time stamp of the log message
- * @param[in] message - log message text
- *
- * @throw ZlibException in case of errors
- */
- void write(time_t timeStamp, const std::string& message) const;
-
- private:
- /** @brief File name. */
- std::string fileName_;
- /** @brief zLib file descriptor. */
- gzFile fd_;
-};
diff --git a/src/log_manager.cpp b/src/log_manager.cpp
deleted file mode 100644
index fde2f6c..0000000
--- a/src/log_manager.cpp
+++ /dev/null
@@ -1,279 +0,0 @@
-/**
- * @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 "log_manager.hpp"
-
-#include "config.hpp"
-
-#include <dirent.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/un.h>
-#include <unistd.h>
-
-#include <cstring>
-#include <set>
-#include <vector>
-
-// 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) - sizeof(sa.sun_path) +
- sizeof(HOSTLOG_SOCKET_PATH) - 1);
- 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_.save(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
deleted file mode 100644
index 3513dac..0000000
--- a/src/log_manager.hpp
+++ /dev/null
@@ -1,106 +0,0 @@
-/**
- * @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
deleted file mode 100644
index ff9b213..0000000
--- a/src/log_storage.cpp
+++ /dev/null
@@ -1,151 +0,0 @@
-/**
- * @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 "log_storage.hpp"
-
-#include "config.hpp"
-#include "log_file.hpp"
-
-#include <fcntl.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include <exception>
-
-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::save(const char* fileName) const
-{
- int rc = 0;
-
- if (empty())
- {
- printf("No messages to write\n");
- return 0;
- }
-
- try
- {
- LogFile log(fileName);
-
- // Write full datetime stamp as the first record
- const time_t& tmStart = messages_.begin()->timeStamp;
- tm tmLocal;
- localtime_r(&tmStart, &tmLocal);
- char tmText[20]; // size of "%F %T" asciiz (YYYY-MM-DD HH:MM:SS)
- strftime(tmText, sizeof(tmText), "%F %T", &tmLocal);
- std::string titleMsg = ">>> Log collection started at ";
- titleMsg += tmText;
- log.write(tmStart, titleMsg);
-
- // Write messages
- for (auto it = messages_.begin(); it != messages_.end(); ++it)
- log.write(it->timeStamp, it->text);
-
- log.close();
- }
- catch (std::exception& e)
- {
- rc = EIO;
- fprintf(stderr, "%s\n", e.what());
- }
-
- return rc;
-}
-
-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
deleted file mode 100644
index 5361c88..0000000
--- a/src/log_storage.hpp
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
- * @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 <zlib.h>
-
-#include <ctime>
-#include <list>
-#include <string>
-
-/** @class LogStorage
- * @brief Log storage implementation.
- * All functions within this class are not thread-safe.
- */
-class LogStorage
-{
- public:
- /** @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 save(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 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_ = true;
-};
diff --git a/src/main.cpp b/src/main.cpp
index 89b4f58..cb8d2bc 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,201 +1,77 @@
-/**
- * @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.
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
#include "config.hpp"
-#include "dbus_server.hpp"
-#include "dbus_watch.hpp"
-#include "log_manager.hpp"
+#include "service.hpp"
+#include "version.hpp"
#include <getopt.h>
-#include <climits>
-#include <cstdio>
-#include <cstdlib>
-
-// 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()
+/** @brief Print version info. */
+static void printVersion()
{
- printf("Host logger service " PACKAGE_VERSION ".\n");
+ puts("Host logger service rev." HOSTLOGGER_VERSION ".");
}
-/** @brief Print help usage info.
+/**
+ * @brief Print help usage info.
*
- * @param[in] app - application's file name
+ * @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);
+ printVersion();
+ puts("Copyright (c) 2020 YADRO.");
+ printf("Usage: %s [OPTION...]\n", app);
+ puts(" -v, --version Print version and exit");
+ puts(" -h, --help Print this help and exit");
}
/** @brief Application entry point. */
int main(int argc, char* argv[])
{
- int opt_val;
// clang-format off
- 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 }
+ const struct option longOpts[] = {
+ { "version", no_argument, nullptr, 'v' },
+ { "help", no_argument, nullptr, 'h' },
+ { nullptr, 0, nullptr, 0 }
};
// clang-format on
-
- opterr = 0;
- while ((opt_val = getopt_long(argc, argv, "p:s:t:f:r:vh", opts, NULL)) !=
- -1)
+ const char* shortOpts = "vh";
+ opterr = 0; // prevent native error messages
+ int val;
+ while ((val = getopt_long(argc, argv, shortOpts, longOpts, nullptr)) != -1)
{
- switch (opt_val)
+ switch (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();
+ printVersion();
return EXIT_SUCCESS;
case 'h':
printHelp(argv[0]);
return EXIT_SUCCESS;
default:
- fprintf(stderr, "Invalid option: %s\n", argv[optind - 1]);
+ fprintf(stderr, "Invalid argument: %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)
+ if (optind < argc)
{
- fprintf(stderr, "Error occurred during the sd_event_default: %i\n", rc);
+ fprintf(stderr, "Unexpected argument: %s\n", argv[optind - 1]);
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)
+ try
+ {
+ Config cfg;
+ Service svc(cfg);
+ svc.run();
+ }
+ catch (const std::exception& ex)
+ {
+ fprintf(stderr, "%s\n", ex.what());
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
+ return EXIT_SUCCESS;
}
diff --git a/src/service.cpp b/src/service.cpp
new file mode 100644
index 0000000..eda8545
--- /dev/null
+++ b/src/service.cpp
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
+
+#include "service.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+#include <vector>
+
+using namespace phosphor::logging;
+
+// clang-format off
+/** @brief Host state properties. */
+static const DbusLoop::WatchProperties watchProperties{
+ {"xyz.openbmc_project.State.Host", {{
+ "RequestedHostTransition", {
+ "xyz.openbmc_project.State.Host.Transition.On"}}}},
+ {"xyz.openbmc_project.State.OperatingSystem.Status", {{
+ "OperatingSystemState", {
+ "xyz.openbmc_project.State.OperatingSystem.Status.OSStatus.BootComplete",
+ "xyz.openbmc_project.State.OperatingSystem.Status.OSStatus.Inactive"}}}}
+};
+// clang-format on
+
+Service::Service(const Config& config) :
+ config(config), hostConsole(config.socketId),
+ logBuffer(config.bufMaxSize, config.bufMaxTime),
+ fileStorage(config.outDir, config.socketId, config.maxFiles)
+{}
+
+void Service::run()
+{
+ if (config.bufFlushFull)
+ {
+ logBuffer.setFullHandler([this]() { this->flush(); });
+ }
+
+ hostConsole.connect();
+
+ // Add SIGUSR1 signal handler for manual flushing
+ dbusLoop.addSignalHandler(SIGUSR1, [this]() { this->flush(); });
+ // Add SIGTERM signal handler for service shutdown
+ dbusLoop.addSignalHandler(SIGTERM, [this]() { this->dbusLoop.stop(0); });
+
+ // Register callback for socket IO
+ dbusLoop.addIoHandler(hostConsole, [this]() { this->readConsole(); });
+
+ // Register host state watcher
+ if (*config.hostState)
+ {
+ dbusLoop.addPropertyHandler(config.hostState, watchProperties,
+ [this]() { this->flush(); });
+ }
+
+ if (!*config.hostState && !config.bufFlushFull)
+ {
+ log<level::WARNING>("Automatic flush disabled");
+ }
+
+ log<level::DEBUG>("Initialization complete",
+ entry("SocketId=%s", config.socketId),
+ entry("BufMaxSize=%lu", config.bufMaxSize),
+ entry("BufMaxTime=%lu", config.bufMaxTime),
+ entry("BufFlushFull=%s", config.bufFlushFull ? "y" : "n"),
+ entry("HostState=%s", config.hostState),
+ entry("OutDir=%s", config.outDir),
+ entry("MaxFiles=%lu", config.maxFiles));
+
+ // Run D-Bus event loop
+ const int rc = dbusLoop.run();
+ if (!logBuffer.empty())
+ {
+ flush();
+ }
+ if (rc < 0)
+ {
+ std::error_code ec(-rc, std::generic_category());
+ throw std::system_error(ec, "Error in event loop");
+ }
+}
+
+void Service::flush()
+{
+ if (logBuffer.empty())
+ {
+ log<level::INFO>("Ignore flush: buffer is empty");
+ return;
+ }
+ try
+ {
+ const std::string fileName = fileStorage.save(logBuffer);
+ logBuffer.clear();
+
+ std::string msg = "Host logs flushed to ";
+ msg += fileName;
+ log<level::INFO>(msg.c_str());
+ }
+ catch (const std::exception& ex)
+ {
+ log<level::ERR>(ex.what());
+ }
+}
+
+void Service::readConsole()
+{
+ constexpr size_t bufSize = 128; // enough for most line-oriented output
+ std::vector<char> bufData(bufSize);
+ char* buf = bufData.data();
+
+ try
+ {
+ while (const size_t rsz = hostConsole.read(buf, bufSize))
+ {
+ logBuffer.append(buf, rsz);
+ }
+ }
+ catch (const std::system_error& ex)
+ {
+ log<level::ERR>(ex.what());
+ }
+}
diff --git a/src/service.hpp b/src/service.hpp
new file mode 100644
index 0000000..86c02c7
--- /dev/null
+++ b/src/service.hpp
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
+
+#pragma once
+
+#include "config.hpp"
+#include "dbus_loop.hpp"
+#include "file_storage.hpp"
+#include "host_console.hpp"
+#include "log_buffer.hpp"
+
+/**
+ * @class Service
+ * @brief Log service: watches for events and handles them.
+ */
+class Service
+{
+ public:
+ /**
+ * @brief Constructor.
+ *
+ * @param[in] config service configuration
+ *
+ * @throw std::exception in case of errors
+ */
+ Service(const Config& config);
+
+ /**
+ * @brief Run the service.
+ *
+ * @throw std::exception in case of errors
+ */
+ void run();
+
+ private:
+ /**
+ * @brief Flush log buffer to a file.
+ */
+ void flush();
+
+ /**
+ * @brief Read data from host console and put it into the log buffer.
+ */
+ void readConsole();
+
+ private:
+ /** @brief Service configuration. */
+ const Config& config;
+ /** @brief D-Bus event loop. */
+ DbusLoop dbusLoop;
+ /** @brief Host console connection. */
+ HostConsole hostConsole;
+ /** @brief Intermediate storage: container for parsed log messages. */
+ LogBuffer logBuffer;
+ /** @brief Persistent storage. */
+ FileStorage fileStorage;
+};
diff --git a/src/version.hpp.in b/src/version.hpp.in
new file mode 100644
index 0000000..99fe644
--- /dev/null
+++ b/src/version.hpp.in
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
+
+#pragma once
+
+#define HOSTLOGGER_VERSION "@VCS_TAG@"
diff --git a/src/zlib_exception.cpp b/src/zlib_exception.cpp
index e88c7b3..1769700 100644
--- a/src/zlib_exception.cpp
+++ b/src/zlib_exception.cpp
@@ -1,22 +1,5 @@
-/**
- * @brief zLib exception.
- *
- * This file is part of HostLogger project.
- *
- * Copyright (c) 2020 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.
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
#include "zlib_exception.hpp"
@@ -52,26 +35,26 @@
details += ')';
}
- what_ = "Unable to ";
+ errDesc = "Unable to ";
switch (op)
{
- case Create:
- what_ += "create";
+ case create:
+ errDesc += "create";
break;
- case Close:
- what_ += "close";
+ case close:
+ errDesc += "close";
break;
- case Write:
- what_ += "write";
+ case write:
+ errDesc += "write";
break;
}
- what_ += " file ";
- what_ += fileName;
- what_ += ": ";
- what_ += details;
+ errDesc += " file ";
+ errDesc += fileName;
+ errDesc += ": ";
+ errDesc += details;
}
const char* ZlibException::what() const noexcept
{
- return what_.c_str();
+ return errDesc.c_str();
}
diff --git a/src/zlib_exception.hpp b/src/zlib_exception.hpp
index b147301..54a57ea 100644
--- a/src/zlib_exception.hpp
+++ b/src/zlib_exception.hpp
@@ -1,22 +1,5 @@
-/**
- * @brief zLib exception.
- *
- * This file is part of HostLogger project.
- *
- * Copyright (c) 2020 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.
- */
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
#pragma once
@@ -25,8 +8,9 @@
#include <exception>
#include <string>
-/** @class ZlibException
- * @brief zLib exception.
+/**
+ * @class ZlibException
+ * @brief zLib exception.
*/
class ZlibException : public std::exception
{
@@ -34,17 +18,18 @@
/** @brief File operation types. */
enum Operation
{
- Create,
- Write,
- Close
+ create,
+ write,
+ close
};
- /** @brief Constructor.
+ /**
+ * @brief Constructor.
*
- * @param[in] op - type of operation
- * @param[in] code - zLib status code
- * @param[in] fd - zLib file descriptor
- * @param[in] fileName - file name
+ * @param[in] op type of operation
+ * @param[in] code zLib status code
+ * @param[in] fd zLib file descriptor
+ * @param[in] fileName file name
*/
ZlibException(Operation op, int code, gzFile fd,
const std::string& fileName);
@@ -54,5 +39,5 @@
private:
/** @brief Error description buffer. */
- std::string what_;
+ std::string errDesc;
};
diff --git a/src/zlib_file.cpp b/src/zlib_file.cpp
new file mode 100644
index 0000000..042dafb
--- /dev/null
+++ b/src/zlib_file.cpp
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
+
+#include "zlib_file.hpp"
+
+#include "zlib_exception.hpp"
+
+ZlibFile::ZlibFile(const std::string& fileName)
+{
+ fd = gzopen(fileName.c_str(), "w");
+ if (fd == Z_NULL)
+ {
+ throw ZlibException(ZlibException::create, Z_ERRNO, fd, fileName);
+ }
+ this->fileName = fileName;
+}
+
+ZlibFile::~ZlibFile()
+{
+ if (fd != Z_NULL)
+ {
+ gzclose_w(fd);
+ }
+}
+
+void ZlibFile::close()
+{
+ if (fd != Z_NULL)
+ {
+ const int rc = gzclose_w(fd);
+ if (rc != Z_OK)
+ {
+ throw ZlibException(ZlibException::close, rc, fd, fileName);
+ }
+ fd = Z_NULL;
+ fileName.clear();
+ }
+}
+
+void ZlibFile::write(const tm& timeStamp, const std::string& message) const
+{
+ int rc;
+
+ // Write time stamp
+ rc = gzprintf(fd, "[ %02i:%02i:%02i ] ", timeStamp.tm_hour,
+ timeStamp.tm_min, timeStamp.tm_sec);
+ if (rc <= 0)
+ {
+ throw ZlibException(ZlibException::write, rc, fd, fileName);
+ }
+
+ // Write message
+ const size_t len = message.length();
+ if (len)
+ {
+ rc = gzwrite(fd, message.data(), static_cast<unsigned int>(len));
+ if (rc <= 0)
+ {
+ throw ZlibException(ZlibException::write, rc, fd, fileName);
+ }
+ }
+
+ // Write EOL
+ rc = gzputc(fd, '\n');
+ if (rc <= 0)
+ {
+ throw ZlibException(ZlibException::write, rc, fd, fileName);
+ }
+}
diff --git a/src/zlib_file.hpp b/src/zlib_file.hpp
new file mode 100644
index 0000000..5f501ab
--- /dev/null
+++ b/src/zlib_file.hpp
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
+
+#pragma once
+
+#include <zlib.h>
+
+#include <ctime>
+#include <string>
+
+/**
+ * @class ZlibFile
+ * @brief Log file writer.
+ */
+class ZlibFile
+{
+ public:
+ /**
+ * @brief Constructor create new file for writing logs.
+ *
+ * @param[in] fileName path to the file
+ *
+ * @throw ZlibException in case of errors
+ */
+ ZlibFile(const std::string& fileName);
+
+ ~ZlibFile();
+
+ ZlibFile(const ZlibFile&) = delete;
+ ZlibFile& operator=(const ZlibFile&) = delete;
+
+ /**
+ * @brief Close file.
+ *
+ * @throw ZlibException in case of errors
+ */
+ void close();
+
+ /**
+ * @brief Write single log message to the file.
+ *
+ * @param[in] timeStamp time stamp of the log message
+ * @param[in] message log message text
+ *
+ * @throw ZlibException in case of errors
+ */
+ void write(const tm& timeStamp, const std::string& message) const;
+
+ private:
+ /** @brief File name. */
+ std::string fileName;
+ /** @brief zLib file descriptor. */
+ gzFile fd;
+};