Initial commit

Signed-off-by: Artem Senichev <a.senichev@yadro.com>
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, &lt);
+    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;
+}