/*
 * Copyright (c) 2021-2023 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 <chrono>
#include <regex>
#include <thread>
#include <dirent.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/syscall.h>

#include "applypatch/partition_record.h"
#include "flashd/flashd.h"
#include "log/log.h"
#include "misc_info/misc_info.h"
#include "package/pkg_manager.h"
#include "securec.h"
#include "updater/hardware_fault_retry.h"
#include "updater/updater.h"
#include "updater/updater_const.h"
#include "updater_main.h"
#include "utils.h"

namespace Updater {
using namespace Hpackage;
using namespace Updater::Utils;

void DeleteInstallTimeFile()
{
    const std::string installTimeFilePath = std::string(UPDATER_PATH) + "/" + std::string(INSTALL_TIME_FILE);
    if (access(installTimeFilePath.c_str(), F_OK) != -1) {
        (void)DeleteFile(installTimeFilePath);
        LOG(INFO) << "delete install time file";
    }
}
 
void WriteInstallTime(UpdaterParams &upParams)
{
    std::ofstream ofs;
    ofs.open(std::string(UPDATER_PATH) + "/" + std::string(INSTALL_TIME_FILE), std::ios::app | std::ios::out);
    if (!ofs.is_open()) {
        LOG(ERROR) << "open install time file fail";
        return;
    }
    ofs << DurationToString(upParams.installTime, upParams.pkgLocation) << "\n";
}
 
void ReadInstallTime(UpdaterParams &upParams)
{
    std::ifstream ifs;
    std::string buf;
    ifs.open(std::string(UPDATER_PATH) + "/" + std::string(INSTALL_TIME_FILE), std::ios::in);
    if (!ifs.is_open()) {
        LOG(ERROR) << "read install time file fail";
        return;
    }
    unsigned int index = 0;
    while (getline(ifs, buf)) {
        if (index >= upParams.pkgLocation) {
            break;
        }
        double timeDistance = 0;
        if (!Utils::ConvertToDouble(buf, timeDistance)) {
            LOG(ERROR) << "ConvertToDouble failed";
            return;
        }
        upParams.installTime[index++] = std::chrono::duration<double>(timeDistance);
    }
}

bool DeleteUpdaterPath(const std::string &path)
{
    auto pDir = std::unique_ptr<DIR, decltype(&closedir)>(opendir(path.c_str()), closedir);
    if (pDir == nullptr) {
        LOG(INFO) << "Can not open dir";
        return true;
    }
    bool sdcardTmp = false;
    if (path.find("sdcard") != std::string::npos) {
        sdcardTmp = true;
    }
    struct dirent *dp = nullptr;
    while ((dp = readdir(pDir.get())) != nullptr) {
        std::string currentName(dp->d_name);
        if (currentName[0] == '.' || (currentName.compare("log") == 0) ||
            (currentName.compare(UPDATER_RESULT_FILE) == 0) ||
            (currentName.compare(UPDATER_LOCALE_FILE) == 0) ||
            (currentName.compare(MODULE_UPDATE_RESULT_FILE) == 0) ||
            (currentName.compare(UPLOAD_LOG_TIME_FILE) == 0) ||
            (currentName.compare(MANUAL_UPLOAD_LOG_TIME_FILE) == 0) ||
            (currentName.compare(INSTALL_TIME_FILE) == 0) ||
            (currentName.compare(ROLLBACK_FILE) == 0) ||
            (currentName.compare(SINGULAR_UPDATE_NAME) == 0) ||
            (currentName.compare(SINGULAR_UPDATE_RESULT) == 0)) {
            continue;
        }
        if (sdcardTmp && currentName.find(SDCARD_PACKAGE_SUFFIX) != std::string::npos) {
            continue;
        }
        std::string tmpName(path);
        tmpName.append("/" + currentName);
        if (IsDirExist(tmpName)) {
            DeleteUpdaterPath(tmpName);
        }
#ifndef UPDATER_UT
        remove(tmpName.c_str());
#endif
    }
    return true;
}

bool ClearMisc()
{
    struct UpdateMessage cleanBoot {};
    if (!WriteUpdaterMiscMsg(cleanBoot)) {
        LOG(ERROR) << "ClearMisc clear boot message to misc failed";
        return false;
    }
    auto miscBlockDev = GetBlockDeviceByMountPoint(MISC_PATH);
    if (miscBlockDev.empty()) {
        LOG(INFO) << "cannot get block device of partition";
        miscBlockDev = MISC_FILE;
    }
    LOG(INFO) << "ClearMisc::misc path : " << miscBlockDev;
    auto fp = std::unique_ptr<FILE, decltype(&fclose)>(fopen(miscBlockDev.c_str(), "rb+"), fclose);
    if (fp == nullptr) {
        LOG(ERROR) << "WriteVersionCode fopen failed" << " : " << strerror(errno);
        return false;
    }
    if (fseek(fp.get(), PARTITION_RECORD_OFFSET, SEEK_SET) != 0) {
        LOG(ERROR) << "ClearMisc fseek failed";
        return false;
    }
    off_t clearOffset = 0;
    if (fwrite(&clearOffset, sizeof(off_t), 1, fp.get()) != 1) {
        LOG(ERROR) << "ClearMisc write misc initOffset 0 failed" << " : " << strerror(errno);
        return false;
    }

    struct PartitionRecordInfo cleanPartition {};
    for (size_t tmpOffset = 0; tmpOffset < PARTITION_UPDATER_RECORD_MSG_SIZE; tmpOffset +=
        sizeof(PartitionRecordInfo)) {
        if (fseek(fp.get(), PARTITION_RECORD_START + tmpOffset, SEEK_SET) != 0) {
            LOG(ERROR) << "ClearMisc fseek failed";
            return false;
        }
        if (fwrite(&cleanPartition, sizeof(PartitionRecordInfo), 1, fp.get()) != 1) {
            LOG(ERROR) << "ClearMisc write misc cleanPartition failed" << " : " << strerror(errno);
            return false;
        }
    }
    return true;
}

bool IsSDCardExist(const std::string &sdcardPath)
{
    // Record system error codes.
    int save_errno = errno;
    struct stat st {};
    if (stat(sdcardPath.c_str(), &st) < 0) {
        return false;
    } else {
        errno = save_errno;
        return true;
    }
}

bool IsMountDataAndSaveLogs(void)
{
    bool isSdCardMode = CheckUpdateMode(SDCARD_MODE);
    bool isUsbMode = CheckUpdateMode(USB_MODE);
    bool isSdCardIntralMode = CheckUpdateMode(SDCARD_INTRAL_MODE);
    bool isLogMounted = GetMountStatusForMountPoint("/log") == MountStatus::MOUNT_MOUNTED;
    bool isDataAlreadyMounted = GetMountStatusForMountPoint("/data") == MountStatus::MOUNT_MOUNTED;
    return (!(isSdCardMode || isUsbMode) && (isDataAlreadyMounted || !isLogMounted)) || isSdCardIntralMode;
}

static void DeleteUpdaterTmpFiles()
{
    if (access(UPDATER_PATH, 0) == 0 && access(SDCARD_CARD_PATH, 0) != 0 && !DeleteUpdaterPath(UPDATER_PATH)) {
        LOG(ERROR) << "DeleteUpdaterPath failed";
    }
    if (access(SDCARD_CARD_PATH, 0) == 0 && !DeleteUpdaterPath(SDCARD_CARD_PATH)) {
        LOG(ERROR) << "Delete sdcard path failed";
    }
    if (access(Flashd::FLASHD_FILE_PATH, 0) == 0 && !DeleteUpdaterPath(Flashd::FLASHD_FILE_PATH)) {
        LOG(ERROR) << "DeleteUpdaterPath failed";
    }
}

void PostUpdater(bool clearMisc)
{
    STAGE(UPDATE_STAGE_BEGIN) << "PostUpdater";
    bool isMountDataAndSaveLogs = IsMountDataAndSaveLogs();
    (void)SetupPartitions(isMountDataAndSaveLogs);
    UpdaterInit::GetInstance().InvokeEvent(UPDATER_POST_INIT_EVENT);
    // clear update misc partition.
    if (clearMisc && !ClearMisc()) {
        LOG(ERROR) << "PostUpdater clear misc failed";
    }
    RecordBatteryLevel();
    if (!access(COMMAND_FILE, 0) && unlink(COMMAND_FILE) != 0) {
        LOG(ERROR) << "Delete command failed";
    }
    if (!HardwareFaultRetry::GetInstance().IsRetry()) {
        DeleteUpdaterTmpFiles();
    }
    if (isMountDataAndSaveLogs) {
        SaveLogs();
    }
}

void BootMode::InitMode(void) const
{
    InitLogger(modeName, true);
#ifdef UPDATER_BUILD_VARIANT_USER
    SetLogLevel(INFO);
#else
    SetLogLevel(DEBUG);
#endif
    LoadFstab(true);
    STAGE(UPDATE_STAGE_OUT) << "Start " << modeName;
    SetParameter(modePara.c_str(), "1");
}

bool IsUpdater(const UpdateMessage &boot)
{
    return !IsFlashd(boot) && strncmp(boot.command, "boot_updater", sizeof("boot_updater") - 1) == 0;
}

bool IsFlashd(const UpdateMessage &boot)
{
    return strncmp(boot.update, "boot_flash", sizeof("boot_flash") - 1) == 0;
}

std::vector<BootMode> &GetBootModes(void)
{
    static std::vector<BootMode> bootModes {};
    return bootModes;
}

void RegisterMode(const BootMode &mode)
{
    GetBootModes().push_back(mode);
}

std::optional<BootMode> SelectMode(const UpdateMessage &boot)
{
    const auto &modes = GetBootModes();

    // select modes by bootMode.cond which would check misc message
    auto it = std::find_if(modes.begin(), modes.end(), [&boot] (const auto &bootMode) {
        if (bootMode.cond != nullptr && bootMode.cond(boot)) {
            LOG(INFO) << "condition for mode " << bootMode.modeName << " is satisfied";
            return true;
        }
        LOG(WARNING) << "condition for mode " << bootMode.modeName << " is not satisfied";
        return false;
    });
    // misc check failed for each mode, then enter updater mode
    if (it == modes.end() || it->entryFunc == nullptr) {
        LOG(WARNING) << "find valid mode failed, enter updater Mode";
        return std::nullopt;
    }

    LOG(INFO) << "enter " << it->modeName << " mode";
    return *it;
}

bool SetCpuAffinityByPid(const UpdaterParams &upParams, unsigned int reservedCores)
{
    static std::mutex setAffinityLock;
    std::lock_guard<std::mutex> lock(setAffinityLock);
    std::vector<std::string> binaryTids = upParams.binaryTids;
    if (upParams.binaryPid == -1 || std::find(
        binaryTids.begin(), binaryTids.end(), std::to_string(upParams.binaryPid)) == binaryTids.end()) {
        LOG(WARNING) << "invalid binaryPid:" << upParams.binaryPid;
        return false;
    }
    unsigned int coreCount = std::thread::hardware_concurrency();
    LOG(INFO) << "coreCount:" << coreCount << ", reservedCores:" << reservedCores;
    if (coreCount <= reservedCores) {
        LOG(WARNING) << "coreCount:" << coreCount << " <= " << reservedCores;
        return false;
    }
    cpu_set_t mask;
    CPU_ZERO(&mask);
    for (unsigned int i = 0; i < coreCount - reservedCores; i++) {
        CPU_SET(i, &mask);
    }
    int syscallRes;
    for (auto &str : binaryTids) {
        int32_t temp = -1;
        if (!Utils::ConvertToLong(str, temp)) {
            LOG(ERROR) << "ConvertToLong failed:" << str;
            continue;
        }
        pid_t tid = static_cast<pid_t>(temp);
        syscallRes = syscall(__NR_sched_setaffinity, tid, sizeof(mask), &mask);
        LOG(INFO) << "setaffinity tid:" << tid;
        if (syscallRes != 0) {
            LOG(ERROR) << "set affinity faild:" << syscallRes;
        }
    }
    return true;
}

void UpdateBinaryTids(const std::vector<std::string> &output, UpdaterParams &upParams)
{
    if (output.size() < DEFAULT_PROCESS_NUM) {
        LOG(ERROR) << "check output fail";
        return;
    }
    auto outputInfo = Trim(output[1]);
    LOG(INFO) << "binary tids:" << outputInfo;
    upParams.binaryTids = SplitString(outputInfo, ",");
    if (upParams.isLoadReduction) {
        unsigned int coreCount = std::thread::hardware_concurrency();
		GetCpuCoresType(upParams);
		// get little core nums or affinityCpuCores
		unsigned int maskedCores = upParams.affinityCpuCores.has_value() ? *upParams.affinityCpuCores :
    								GetCpuCores(upParams, LITTLE_CORE_INDEX);
		unsigned int reservedCores = coreCount > maskedCores ? coreCount - maskedCores : 0;
        SetCpuAffinityByPid(upParams, reservedCores);
    }
}

unsigned int GetCpuCores(UpdaterParams &upParams, int cpuType)
{
    if (cpuType < 0 || cpuType >= static_cast<int>(upParams.cpuTypeCores.size())) {
        return cpuType == BIG_CORE_INDEX ? DEFAULT_BIG_CORES : DEFAULT_LITTLE_CORES;
    }
    return upParams.cpuTypeCores[cpuType];
}

void GetCpuCoresType(UpdaterParams &upParams)
{
    if (!upParams.cpuTypeCores.empty()) {
        return;
    }
    upParams.cpuTypeCores.resize(CPU_CATEGORIES);
    for (uint32_t i = 0; i < CPU_CATEGORIES; ++i) {
        std::string cpuCoreNode = CPU_CORE_POLICY_NODE_DIR + std::to_string(i) + "/" + AFFECTED_CPUS;
        std::ifstream fin(cpuCoreNode, std::ios::in);
        if (!fin.is_open()) {
            LOG(ERROR) << "open " << cpuCoreNode << " failed, error: " << errno;
            upParams.cpuTypeCores[i] = 0;
            continue;
        }
        std::string cpuCoreStr {std::istreambuf_iterator<char> {fin}, {}};
        cpuCoreStr = Trim(cpuCoreStr);
        LOG(INFO) << "node " << i << " content is: " << cpuCoreStr;
        upParams.cpuTypeCores[i] = Utils::SplitString(cpuCoreStr, " ").size();
    }
    return;
}
} // namespace Updater