/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022. All rights reserved.
 * 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 <ctime>
#include <sstream>
#include <sys/time.h>
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>

#include "bpf_log_reader.h"

static constexpr int FILE_MODE = 0644;

BPFLogReader::~BPFLogReader()
{
    Stop();
    if (worker_.joinable()) {
        worker_.join();
    }
    if (ifd_ >= 0) {
        close(ifd_);
        ifd_ = -1;
    }
    if (ofd_ >= 0) {
        close(ofd_);
        ofd_ = -1;
    }
}

std::unique_ptr<BPFLogReader> BPFLogReader::MakeUnique(const std::string& logFile)
{
    std::unique_ptr<BPFLogReader> logReader {new(std::nothrow) BPFLogReader {}};
    if (logReader == nullptr) {
        return nullptr;
    }
    if (logReader->EnableTracePipe() != 0) {
        return nullptr;
    }
    if (logReader->OpenTracePipe() != 0) {
        return nullptr;
    }
    if (logReader->OpenLogFile(logFile) != 0) {
        return nullptr;
    }
    return logReader;
}

std::string BPFLogReader::GetLogFileName() const
{
    struct timeval timer;
    gettimeofday(&timer, nullptr);
    time_t now = (time_t) timer.tv_sec;
    struct tm* tmPtr {nullptr};
    tmPtr = localtime(&now);
    if (tmPtr == nullptr) {
        return "";
    }
    std::stringstream ss;
    constexpr int yearStart {1900};
    constexpr int monthStart {1};
    ss << "/data/local/tmp/";
    ss << std::to_string(tmPtr->tm_year + yearStart) << ".";
    ss << std::to_string(tmPtr->tm_mon + monthStart) << ".";
    ss << std::to_string(tmPtr->tm_mday) << "_";
    ss << std::to_string(tmPtr->tm_hour) << ".";
    ss << std::to_string(tmPtr->tm_min) << ".";
    ss << std::to_string(tmPtr->tm_sec) << ".bpf.log";
    return ss.str();
}

int BPFLogReader::OpenLogFile(const std::string& logFile)
{
    if (logFile.compare("stdout") == 0) {
        if (fcntl(STDOUT_FILENO, F_GETFL)) {
            ofd_ = open("/dev/stdout", O_WRONLY);
        } else {
            ofd_ = STDOUT_FILENO;
        }
        if (ofd_ < 0) {
            return -1;
        }
        return 0;
    }
    auto fileName = GetLogFileName();
    if (fileName.length() == 0) {
        return -1;
    }
    ofd_ = open(fileName.c_str(), O_WRONLY | O_CREAT, FILE_MODE);
    if (ofd_ < 0) {
        return -1;
    }
    unlink(logFile.c_str());
    if (link(fileName.c_str(), logFile.c_str()) != 0) {
        return -1;
    }

    return 0;
}

int BPFLogReader::EnableTracePipe() const
{
    int fd = open(confPath_.c_str(), O_WRONLY);
    if (fd < 0) {
        return -1;
    }
    if (ftruncate(fd, 0) != 0) {
    }
    char c {'1'};
    int ret = write(fd, &c, sizeof(c));
    if (ret != sizeof(c)) {
        close(fd);
        return -1;
    }
    close(fd);

    return 0;
}

int BPFLogReader::OpenTracePipe()
{
    ifd_ = open(pipePath_.c_str(), O_RDONLY | O_NONBLOCK);
    if (ifd_ < 0) {
        return -1;
    }
    return 0;
}

int BPFLogReader::MoveBPFLog()
{
    constexpr size_t bufSize {1024};
    char buffer[bufSize];
    while (true) {
        struct pollfd fds;
        fds.fd = ifd_;
        fds.events = POLLIN;
        constexpr int timeout {10};
        constexpr nfds_t nfds {1};
        int ret = poll(&fds, nfds, timeout);
        if (stop_ or ret == -1) {
            break;
        }
        if (ret == nfds) {
            ret = read(ifd_, buffer, sizeof(buffer));
            if (ret <= 0) {
                break;
            }
            ret = write(ofd_, buffer, ret);
            if (ret <= 0) {
                break;
            }
        }
    }
    return 0;
}