/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * 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 "utils_fs.h"
#include <algorithm>
#include <cerrno>
#include <cstdint>
#include <cstdlib>
#include <dirent.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <limits>
#include <linux/reboot.h>
#include <string>
#include <sys/reboot.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <vector>
#include "log/log.h"

namespace Updater {
namespace Utils {
constexpr long MAX_FILE_LENGTH = 4 * 1024 * 1024;

int MkdirRecursive(const std::string &pathName, mode_t mode)
{
    size_t slashPos = 0;
    struct stat info {};
    while (true) {
        slashPos = pathName.find_first_of("/", slashPos);
        if (slashPos == std::string::npos) {
            break;
        }
        if (slashPos == 0) {
            slashPos++;
            continue;
        }
        if (slashPos > PATH_MAX) {
            LOG(ERROR) << "path too long for mkdir";
            return -1;
        }
        auto subDir = pathName.substr(0, slashPos);
        LOG(DEBUG) << "subDir : " << subDir;
        if (stat(subDir.c_str(), &info) != 0) {
            int ret = mkdir(subDir.c_str(), mode);
            if (ret && errno != EEXIST) {
                return ret;
            }
        }
        slashPos++;
    }
    int ret = mkdir(pathName.c_str(), mode);
    if (ret && errno != EEXIST) {
        return ret;
    }
    return 0;
}

int64_t GetFilesFromDirectory(const std::string &path, std::vector<std::string> &files,
    bool isRecursive)
{
    struct stat sb {};
    if (stat(path.c_str(), &sb) == -1) {
        LOG(ERROR) << "Failed to stat";
        return -1;
    }
    DIR *dirp = opendir(path.c_str());
    if (dirp == nullptr) {
        LOG(ERROR) << "Failed to opendir errno=" << errno;
        return -1;
    }
    struct dirent *dp = nullptr;
    int64_t totalSize = 0;
    while ((dp = readdir(dirp)) != nullptr) {
        std::string fileName = path + "/" + dp->d_name;
        struct stat st {};
        if (stat(fileName.c_str(), &st) == 0) {
            std::string tmpName = dp->d_name;
            if (tmpName == "." || tmpName == "..") {
                continue;
            }
            if (isRecursive && S_ISDIR(st.st_mode)) {
                totalSize += GetFilesFromDirectory(fileName, files, isRecursive);
            }
            files.push_back(fileName);
            totalSize += st.st_size;
        }
    }
    closedir(dirp);
    dirp = nullptr;
    return totalSize;
}

bool RemoveDir(const std::string &path)
{
    if (path.empty() || path == "/") {
        LOG(ERROR) << "input path is invalid.";
        return false;
    }
    std::string strPath = path;
    if (strPath.at(strPath.length() - 1) != '/') {
        strPath.append("/");
    }
    DIR *d = opendir(strPath.c_str());
    if (d != nullptr) {
        struct dirent *dt = nullptr;
        dt = readdir(d);
        while (dt != nullptr) {
            if (strcmp(dt->d_name, "..") == 0 || strcmp(dt->d_name, ".") == 0) {
                dt = readdir(d);
                continue;
            }
            struct stat st {};
            auto file_name = strPath + std::string(dt->d_name);
            if (stat(file_name.c_str(), &st) != 0) {
                LOG(ERROR) << "stat fail, errno: " << errno;
            }
            if (S_ISDIR(st.st_mode)) {
                RemoveDir(file_name);
            } else {
                remove(file_name.c_str());
            }
            dt = readdir(d);
        }
        closedir(d);
    }
    return rmdir(strPath.c_str()) == 0 ? true : false;
}

bool IsFileExist(const std::string &path)
{
    struct stat st {};
    if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
        return true;
    }
    return false;
}

bool IsDirExist(const std::string &path)
{
    struct stat st {};
    if (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {
        return true;
    }
    return false;
}

bool ReadFileToString(int fd, std::string &content)
{
    struct stat sb {};
    if (fstat(fd, &sb) != -1 && sb.st_size > 0) {
        content.resize(static_cast<size_t>(sb.st_size));
    }
    ssize_t n;
    auto remaining = static_cast<size_t>(sb.st_size);
    auto p = reinterpret_cast<char *>(content.data());
    while (remaining > 0) {
        n = read(fd, p, remaining);
        if (n <= 0) {
            return false;
        }
        p += n;
        remaining -= static_cast<size_t>(n);
    }
    return true;
}

bool ReadStringFromProcFile(const std::string &filePath, std::string &content)
{
    std::ifstream file(filePath.c_str());
    if (!file.is_open()) {
        LOG(ERROR) << "failed to open " << filePath <<  ", err: " << strerror(errno);
        return false;
    }
 
    file.seekg(0, std::ios::end);
    const long fileLength = file.tellg();
    if (fileLength > MAX_FILE_LENGTH) {
        LOG(ERROR) << "file oversize " << fileLength << ", max is " << MAX_FILE_LENGTH;
        return false;
    }
 
    content.clear();
    file.seekg(0, std::ios::beg);
    std::copy(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>(), std::back_inserter(content));
    return true;
}
} // Utils
} // updater