/******************************************************************************
 * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved.
 * libkperf licensed under the Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v2 for more details.
 * Author: Mr.Wang
 * Create: 2024-04-03
 * Description: Provide common file operation functions and system resource management functions.
 ******************************************************************************/

#include <cstring>
#include <vector>
#include <iostream>
#include <sstream>
#include <fstream>
#include <cstdlib>
#include <unistd.h>
#include <dirent.h>
#include <sys/resource.h>
#include <sys/utsname.h>
#include <sys/stat.h>
#include <climits>
#include <map>
#include <sys/vfs.h>
#include "pcerrc.h"
#include "pcerr.h"
#include "common.h"

const std::string CUR_NS_PATH = "/proc/self/ns/mnt";
const std::string MOUNT_INFO_PATH = "/proc/self/mountinfo";

std::string GetRealPath(const std::string filePath)
{
    char resolvedPath[PATH_MAX];
    if (realpath(filePath.data(), resolvedPath) == nullptr) {
        return std::string{};
    }
    if (access(resolvedPath, R_OK) != 0) {
        return std::string{};
    }
    return resolvedPath;
}

bool IsValidPath(const std::string& filePath)
{
    if (filePath.empty()) {
        return false;
    }
    return true;
}

bool IsDirectory(const std::string& path)
{
    struct stat statbuf;
    return stat(path.c_str(), &statbuf) == 0 && S_ISDIR(statbuf.st_mode);
}

std::vector<std::string> ListDirectoryEntries(const std::string& dirPath)
{
    std::vector<std::string> entries;
    DIR* dir = opendir(dirPath.c_str());
    if (!dir) {
        pcerr::New(LIBPERF_ERR_OPEN_INVALID_FILE, "Failed to open directory: " + dirPath);
        return entries;
    }
    struct dirent* entry;
    while ((entry = readdir(dir)) != nullptr) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
        entries.push_back(entry->d_name);
    }
    closedir(dir);
    return entries;
}

std::string ReadFileContent(const std::string& filePath)
{
    std::ifstream file(filePath);
    if (!file.is_open()) {
        pcerr::New(LIBPERF_ERR_OPEN_INVALID_FILE, "Failed to open File: " + filePath);
        return "";
    }
    std::string content;
    std::getline(file, content);
    file.close();
    return content;
}

bool ExistPath(const std::string &filePath) {
    struct stat statbuf{};
    return stat(filePath.c_str(), &statbuf) == 0;
}

std::vector<std::string> SplitStringByDelimiter(const std::string& str, char delimiter)
{
    std::vector<std::string> parts;
    std::stringstream ss(str);
    std::string part;
    while (std::getline(ss, part, delimiter)) {
        if (!part.empty()) {
            parts.push_back(part);
        }
    }
    return parts;
}

std::string GetTraceEventDir()
{
    if (ExistPath(TRACE_EVENT_PATH)) {
        return TRACE_EVENT_PATH;
    }
    if (ExistPath(TRACE_DEBUG_EVENT_PATH)) {
        return TRACE_DEBUG_EVENT_PATH;
    }
    return "";
}

bool ConvertStrToInt(const std::string &intValStr, int32_t &val)
{
    try {
        val = stoi(intValStr, nullptr, 10);
    } catch (const std::exception &e) {
        return false;
    }
    return true;
}

int GetParanoidVal()
{
    std::string paranoidValStr = ReadFileContent(PERF_EVENT_PARANOID_PATH);
    if (!paranoidValStr.empty()) {
        int val;
        if (ConvertStrToInt(paranoidValStr, val)) {
            return val;
        }
    }
    return INT32_MAX;
}

static int CheckCgroupV2()
{
    const char *mnt = "/sys/fs/cgroup";
    struct statfs stbuf;

    if (statfs(mnt, &stbuf) < 0) {
        return -1;
    }

    return (stbuf.f_type == CGROUP2_SUPER_MAGIC);
}

std::string GetCgroupPath(const std::string& cgroupName) {
    std::string cgroupRootPath = "/sys/fs/cgroup/";
    int cgroupIsV2 = CheckCgroupV2();
    if (cgroupIsV2) {
        cgroupRootPath += cgroupName;
    } else if (cgroupIsV2 == 0) {
        cgroupRootPath += "perf_event/" + cgroupName;
    } else {
        return "";
    }
    return cgroupRootPath;
}

bool CheckCurKernelConfig(const std::string& configName)
{
    std::string configPath;
    struct utsname buff;
    if (uname(&buff) == 0) {
        configPath = KERNEL_CONFIG_BASE_PATH + buff.release;
    } else {
        return false;
    }

    std::ifstream configFile(configPath.c_str());
    if (!configFile.is_open()) {
        return false;
    }

    std::string line;
    while(getline(configFile, line)) {
        if (line.find(configName) != std::string::npos) {
            return true;
        }
    }
    return false;
}

struct MountEntry {
    int mountId;
    int parentId;
    char device[32];
    char root[256];
    char mountPoint[256];
    char options[256];
    char optionFields[256];
    char fsType[32];
    char source[256];
    char superOptions[256];
};

void ParseMountInfo(const std::string& mntPath, std::map<std::string, MountEntry>& overlayMap)
{
    std::ifstream file(mntPath);
    if (!file.is_open()) {
        return;
    }
    std::string line;
    while (std::getline(file, line)){
        struct MountEntry entry;
        int parseLen = sscanf(line.c_str(), "%d %d %31s %255s %255s %255s %255[^-] - %31s %255s %255s", 
                        &entry.mountId, &entry.parentId, entry.device, entry.root, entry.mountPoint, entry.options, entry.optionFields, entry.fsType, entry.source, entry.superOptions);
        if (parseLen != 10) {
            continue;
        }
        if (strcmp(entry.fsType, "overlay") == 0) {
            overlayMap[entry.superOptions] = entry;
        }
    }
}

bool CheckIsSameMnt(int pid)
{
    std::string newMnt = "/proc/" + std::to_string(pid) + "/ns/mnt";
    struct stat curStat;
    struct stat newStat;
    if (stat(CUR_NS_PATH.c_str(), &curStat) < 0) {
        return true;
    }

    if (stat(newMnt.c_str(), &newStat) < 0) {
        return true;
    }

    return curStat.st_ino == newStat.st_ino;
}

std::string GetMntPoint(int pid)
{
    std::string mntPoint;
    if (CheckIsSameMnt(pid)) {
        return mntPoint;
    }
    std::map<std::string, MountEntry> overlayMap;
    std::map<std::string, MountEntry> newOverlayMap;
    ParseMountInfo(MOUNT_INFO_PATH, overlayMap);
    std::string newMntInfo = "/proc/" + std::to_string(pid) + "/mountinfo";
    ParseMountInfo(newMntInfo, newOverlayMap);
    for (const auto& item : newOverlayMap) {
        if (overlayMap.find(item.first) != overlayMap.end()) {
            auto entry = overlayMap[item.first];
            if (strcmp(entry.device, item.second.device) == 0) {
                 mntPoint = entry.mountPoint;
            }
        }
    }
    return mntPoint;
}

static std::string ReadAll(const std::string& path)
{
    std::ifstream in(path);
    if (!in) {
        return "";
    }
    std::ostringstream ss;
    ss << in.rdbuf();
    return ss.str();
}

static bool FileContainsLine(const std::string& path, const std::string& key)
{
    std::ifstream in(path);
    if (!in) {
        return false;
    }

    std::string line;
    while (std::getline(in, line)) {
        if (line.find(key) != std::string::npos) {
            return true;
        }
    }

    return false;
}

bool IsJvmProcess(int pid)
{
    const std::string procPath = "/proc/" + std::to_string(pid);
    const std::string cmdline = ReadAll(procPath + "/cmdline");
    if (cmdline.find("java") != std::string::npos ||
        cmdline.find("jdk") != std::string::npos) {
        return true;
    }
    return FileContainsLine(procPath + "/maps", "libjvm.so");
}