/*

 * Copyright (c) 2021 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 "update_image_block.h"

#include <cerrno>

#include <fcntl.h>

#include <fstream>

#include <pthread.h>

#include <sstream>

#include <sys/stat.h>

#include <sys/types.h>

#include <thread>

#include <unistd.h>

#include "applypatch/block_set.h"

#include "applypatch/store.h"

#include "applypatch/transfer_manager.h"

#include "applypatch/partition_record.h"

#include "fs_manager/mount.h"

#include "log/dump.h"

#include "log/log.h"

#ifdef UPDATER_USE_PTABLE

#include "ptable_manager.h"

#endif

#include "updater/updater_const.h"

#include "updater/hardware_fault_retry.h"

#include "utils.h"

#include "slot_info/slot_info.h"

#include "uncache.h"



using namespace Uscript;

using namespace Hpackage;

using namespace Updater;

using namespace Updater::Utils;



namespace Updater {

constexpr int32_t SHA_CHECK_SECOND = 2;

constexpr int32_t SHA_CHECK_PARAMS = 3;

constexpr int32_t SHA_CHECK_TARGETPAIRS_INDEX = 3;

constexpr int32_t SHA_CHECK_TARGETSHA_INDEX = 4;

constexpr int32_t SHA_CHECK_TARGET_PARAMS = 5;

constexpr int32_t SHA_DELAY_MILLIS_SECOND = 1;

constexpr int32_t SHA_DELAY_CYCLE_COUNT = 250;

constexpr const char *STASHED_PATH = "/data/updater/partitions";

constexpr const char *IMG_SUFFIX = ".img";



__attribute__((weak)) void GetWriteDevPath(const std::string &path, [[maybe_unused]] const std::string &partitionName,

    std::string &devPath)

{

    devPath = path;

    LOG(INFO) << "GetWriteDevPath: " << devPath;

    return;

}



__attribute__((weak)) void SyncWriteDevPath(int fd, [[maybe_unused]] const std::string &partitionName)

{

    if (fd < 0) {

        LOG(ERROR) << "fd is invalid";

        return;

    }

    if (fsync(fd) == -1) {

        LOG(ERROR) << "fsync failed for " << partitionName;

    }

    return;

}



void UpdateFdInfo::Close()

{

    // same fd

    if (sourceFd == targetFd && sourceFd != -1) {

        close(sourceFd);

        sourceFd = -1;

        targetFd = -1;

        return;

    }

    if (sourceFd != -1) {

        close(sourceFd);

        sourceFd = -1;

    }

    if (targetFd != -1) {

        close(targetFd);

        targetFd = -1;

    }

}



static bool BuildSafeStashedDevPath(const std::string &subPath, std::string &stashedDevPath)

{

    const std::string prefix = std::string(STASHED_PATH) + "/";

    std::string fullPath = prefix + subPath + std::string(IMG_SUFFIX);

    if (!Utils::PathToRealPath(fullPath, stashedDevPath)) {

        LOG(WARNING) << "Failed to build stashed dev path for " << subPath;

        return false;

    }

    if (stashedDevPath.find(prefix) != 0) {

        LOG(WARNING) << "Invalid stashed dev path for " << subPath;

        return false;

    }

    return true;

}



static std::string GetPartName(const std::string &partitionName)

{

    if (partitionName.empty()) {

        return "";

    }

    return partitionName[0] == '/' ? partitionName.substr(1) : partitionName;

}



static int ExtractNewData(const PkgBuffer &buffer, size_t size, size_t start, bool isFinish, const void* context)

{

    void *p = const_cast<void *>(context);

    WriterThreadInfo *info = static_cast<WriterThreadInfo *>(p);

    uint8_t *addr = buffer.buffer;

    while (size > 0) {

        pthread_mutex_lock(&info->mutex);

        while (info->writer == nullptr) {

            if (!info->readyToWrite) {

                LOG(WARNING) << "writer is not ready to write.";

                pthread_mutex_unlock(&info->mutex);

                return Hpackage::PKG_INVALID_STREAM;

            }

            pthread_cond_wait(&info->cond, &info->mutex);

        }

        pthread_mutex_unlock(&info->mutex);

        size_t toWrite = std::min(size, info->writer->GetBlocksSize() - info->writer->GetTotalWritten());

        // No more data to write.

        if (toWrite == 0) {

            break;

        }

        bool ret = info->writer->Write(addr, toWrite, nullptr);

        std::ostringstream logMessage;

        logMessage << "Write " << toWrite << " byte(s) failed";

        if (!ret) {

            LOG(ERROR) << logMessage.str();

            return Hpackage::PKG_INVALID_STREAM;

        }

        size -= toWrite;

        addr += toWrite;



        if (info->writer->IsWriteDone()) {

            pthread_mutex_lock(&info->mutex);

            info->writer.reset();

            pthread_cond_broadcast(&info->cond);

            pthread_mutex_unlock(&info->mutex);

        }

    }

    return Hpackage::PKG_SUCCESS;

}



static inline void CondBroadcast(WriterThreadInfo *info)

{

    pthread_mutex_lock(&info->mutex);

    info->readyToWrite = false;

    if (info->writer != nullptr) {

        pthread_cond_broadcast(&info->cond);

    }

    pthread_mutex_unlock(&info->mutex);

}



void* UnpackNewData(void *arg)

{

    TransferManagerPtr tm = static_cast<TransferManagerPtr>(arg);

    WriterThreadInfo *info = tm->GetTransferParams()->writerThreadInfo.get();

    Hpackage::PkgManager::StreamPtr stream = nullptr;

    if (info->newPatch.empty()) {

        LOG(ERROR) << "new patch file name is empty. thread quit.";

        CondBroadcast(info);

        return nullptr;

    }

    LOG(DEBUG) << "new patch file name: " << info->newPatch;

    auto env = tm->GetTransferParams()->env;

    const FileInfo *file = env->GetPkgManager()->GetFileInfo(info->newPatch);

    if (file == nullptr) {

        LOG(ERROR) << "Cannot get file info of :" << info->newPatch;

        CondBroadcast(info);

        return nullptr;

    }

    LOG(DEBUG) << info->newPatch << " info: size " << file->packedSize << " unpacked size " <<

        file->unpackedSize << " name " << file->identity;

    int32_t ret = env->GetPkgManager()->CreatePkgStream(stream, info->newPatch, ExtractNewData, info);

    if (ret != Hpackage::PKG_SUCCESS || stream == nullptr) {

        LOG(ERROR) << "Cannot extract " << info->newPatch << " from package.";

        CondBroadcast(info);

        return nullptr;

    }

    ret = env->GetPkgManager()->ExtractFile(info->newPatch, stream);

    env->GetPkgManager()->ClosePkgStream(stream);

    LOG(DEBUG) << "new data writer ending...";

    // extract new data done.

    // tell command.

    CondBroadcast(info);

    return nullptr;

}



int32_t ReturnAndPushParam(int32_t returnValue, Uscript::UScriptContext &context)

{

    context.PushParam(returnValue);

    return returnValue;

}



int32_t UScriptInstructionBlockUpdate::GetUpdateBlockInfo(struct UpdateBlockInfo &infos, Uscript::UScriptEnv &env,

    Uscript::UScriptContext &context)

{

    if (context.GetParamCount() != 4) { // 4:Determine the number of parameters

        LOG(ERROR) << "Invalid param";

        return ReturnAndPushParam(USCRIPT_INVALID_PARAM, context);

    }



    // Get partition Name first.

    // Use partition name as zip file name. ${partition name}.zip

    // load ${partition name}.zip from updater package.

    // Try to unzip ${partition name}.zip, extract transfer.list, net.dat, patch.dat

    size_t pos = 0;

    int32_t ret = context.GetParam(pos++, infos.partitionName);

    if (ret != USCRIPT_SUCCESS) {

        LOG(ERROR) << "Error to get param 1";

        return ret;

    }

    ret = context.GetParam(pos++, infos.transferName);

    if (ret != USCRIPT_SUCCESS) {

        LOG(ERROR) << "Error to get param 2";

        return ret;

    }

    ret = context.GetParam(pos++, infos.newDataName);

    if (ret != USCRIPT_SUCCESS) {

        LOG(ERROR) << "Error to get param 3";

        return ret;

    }

    ret = context.GetParam(pos++, infos.patchDataName);

    if (ret != USCRIPT_SUCCESS) {

        LOG(ERROR) << "Error to get param 4";

        return ret;

    }



    LOG(INFO) << "ExecuteUpdateBlock::updating  " << infos.partitionName << " ...";

    infos.devPath = GetBlockDeviceByMountPoint(infos.partitionName);

#ifndef UPDATER_UT

    if (infos.partitionName != "/userdata") {

        std::string suffix = Utils::GetUpdateSuffix();

        infos.devPath += suffix;

    }

#else

    infos.devPath = "/data/updater" + infos.partitionName;

#endif

    LOG(INFO) << "ExecuteUpdateBlock::updating  dev path : " << infos.devPath;

    if (infos.devPath.empty()) {

        LOG(ERROR) << "cannot get block device of partition";

        return ReturnAndPushParam(USCRIPT_ERROR_EXECUTE, context);

    }

    return USCRIPT_SUCCESS;

}



bool UScriptInstructionBlockUpdate::ExecuteTransferCommands(TransferManagerPtr tm, const UpdateFdInfo &fdInfo,

    const UpdateBlockInfo &infos, const std::vector<std::string> &lines)

{

    if (tm == nullptr) {

        LOG(ERROR) << "TransferManager is nullptr";

        return false;

    }

    return tm->CommandsParser(fdInfo.sourceFd, fdInfo.targetFd, lines);

}



int32_t UScriptInstructionBlockUpdate::ExecuteTransferCommand(const UpdateFdInfo &fdInfo,

    const std::vector<std::string> &lines, TransferManagerPtr tm, Uscript::UScriptContext &context,

    const UpdateBlockInfo &infos)

{

    auto transferParams = tm->GetTransferParams();

    transferParams->storeBase = GetDiffTempPath() + infos.partitionName + "_tmp";

    transferParams->retryFile = GetDiffTempPath() + infos.partitionName + "_retry";

    transferParams->devPath = infos.devPath;

    LOG(INFO) << "Store base path is " << transferParams->storeBase;

    int32_t ret = Store::CreateNewSpace(transferParams->storeBase, !transferParams->env->IsRetry());

    if (ret == -1) {

        LOG(ERROR) << "Error to create new store space";

        return ReturnAndPushParam(USCRIPT_ERROR_EXECUTE, context);

    }

    transferParams->storeCreated = ret;



    if (!ExecuteTransferCommands(tm, fdInfo, infos, lines)) {

        return USCRIPT_ERROR_EXECUTE;

    }

    JoinThread(tm);

    if (transferParams->storeCreated != -1) {

        Store::DoFreeSpace(transferParams->storeBase);

    }

    return USCRIPT_SUCCESS;

}



void UScriptInstructionBlockUpdate::JoinThread(TransferManagerPtr tm)

{

    if (tm == nullptr) {

        LOG(ERROR) << "transfermanager is null";

        return;

    }

    auto transferParams = tm->GetTransferParams();

    auto writerThreadInfo = transferParams->writerThreadInfo.get();

    pthread_mutex_lock(&writerThreadInfo->mutex);

    if (writerThreadInfo->readyToWrite) {

        LOG(WARNING) << "New data writer thread is still available...";

    }



    writerThreadInfo->readyToWrite = false;

    pthread_cond_broadcast(&writerThreadInfo->cond);

    pthread_mutex_unlock(&writerThreadInfo->mutex);

    int32_t ret = pthread_join(transferParams->thread, nullptr);



    std::ostringstream logMessage;

    logMessage << "pthread join returned with " << ret;

    if (ret != 0) {

        LOG(WARNING) << logMessage.str();

    }

}



int UScriptInstructionBlockUpdate::InitThread(const struct UpdateBlockInfo &infos, TransferManagerPtr tm)

{

    LOG(INFO) << "Ready to start a thread to handle new data processing";

    auto transferParams = tm->GetTransferParams();

    auto writerThreadInfo = transferParams->writerThreadInfo.get();

    writerThreadInfo->readyToWrite = true;

    pthread_mutex_init(&writerThreadInfo->mutex, nullptr);

    pthread_cond_init(&writerThreadInfo->cond, nullptr);

    pthread_attr_t attr;

    pthread_attr_init(&attr);

    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    writerThreadInfo->newPatch = infos.newDataName;

    int error = pthread_create(&transferParams->thread, &attr, UnpackNewData, tm);

    return error;

}



int32_t UScriptInstructionBlockUpdate::ExtractDiffPackageAndLoad(const UpdateBlockInfo &infos, Uscript::UScriptEnv &env,

    Uscript::UScriptContext &context)

{

    Hpackage::PkgManager::StreamPtr outStream = nullptr;

    LOG(DEBUG) << "partitionName is " << infos.partitionName;

    const FileInfo *info = env.GetPkgManager()->GetFileInfo(infos.partitionName);

    if (info == nullptr) {

        LOG(WARNING) << "Error to get file info";

        return USCRIPT_SUCCESS;

    }

    std::string diffPackage = std::string(GetDiffTempPath()) + infos.partitionName;

    int32_t ret = env.GetPkgManager()->CreatePkgStream(outStream,

        diffPackage, info->unpackedSize, PkgStream::PkgStreamType_Write);

    if (outStream == nullptr) {

        LOG(ERROR) << "Error to create output stream";

        return USCRIPT_ERROR_EXECUTE;

    }



    ret = env.GetPkgManager()->ExtractFile(infos.partitionName, outStream);

    if (ret != USCRIPT_SUCCESS) {

        LOG(ERROR) << "Error to extract file";

        env.GetPkgManager()->ClosePkgStream(outStream);

        return USCRIPT_ERROR_EXECUTE;

    }

    env.GetPkgManager()->ClosePkgStream(outStream);

    std::string diffPackageZip = diffPackage + ".zip";

    if (rename(diffPackage.c_str(), diffPackageZip.c_str()) != 0) {

        LOG(ERROR) << "rename failed";

        return USCRIPT_ERROR_EXECUTE;

    }

    LOG(DEBUG) << "Rename " << diffPackage << " to zip\nExtract " << diffPackage << " done\nReload " << diffPackageZip;

    std::vector<std::string> diffPackageComponents;

    ret = env.GetPkgManager()->LoadPackage(diffPackageZip, Updater::Utils::GetCertName(), diffPackageComponents);

    if (diffPackageComponents.size() < 1) {

        LOG(ERROR) << "Diff package is empty";

        return ReturnAndPushParam(USCRIPT_ERROR_EXECUTE, context);

    }

    return USCRIPT_SUCCESS;

}



UpdateFdInfo UScriptInstructionBlockUpdate::CreateFdInfo(const UpdateBlockInfo &infos, TransferManagerPtr tm)

{

    UpdateFdInfo fdInfo;

    std::string devPath = "";

    GetWriteDevPath(infos.devPath, infos.partitionName, devPath);

    std::string dataDevPath = GetStashedPath(infos);

    // ab or vab

    if (devPath != infos.devPath) {

        fdInfo.sourceFd = open(infos.devPath.c_str(), O_RDWR | O_LARGEFILE | O_UNCACHE_FLAG);

        fdInfo.targetFd = open(devPath.c_str(), O_RDWR | O_LARGEFILE | O_UNCACHE_FLAG);

        tm->GetTransferParams()->inPlaceDiff = false;

        return fdInfo;

    }



    std::string targetPartitionPath = infos.devPath;

    size_t targetPartitionOffset = 0;

    if (Utils::IsFileExist(dataDevPath) && GetPartitionInfo(infos.partitionName,

        targetPartitionPath, targetPartitionOffset) && !targetPartitionPath.empty()) {

        // need backup partitions

        fdInfo.sourceFd = open(dataDevPath.c_str(), O_RDWR | O_LARGEFILE | O_UNCACHE_FLAG);

        fdInfo.targetFd = open(targetPartitionPath.c_str(), O_RDWR | O_LARGEFILE | O_UNCACHE_FLAG);

        tm->GetTransferParams()->offset = targetPartitionOffset;

        tm->GetTransferParams()->inPlaceDiff = false;

        usedStashPtn_ = true;

        return fdInfo;

    }

    LOG(DEBUG) << "partition " << infos.partitionName;

    fdInfo.sourceFd = open(infos.devPath.c_str(), O_RDWR | O_LARGEFILE | O_UNCACHE_FLAG);

    fdInfo.targetFd = fdInfo.sourceFd;

    return fdInfo;

}



int32_t UScriptInstructionBlockUpdate::DoExecuteUpdateBlock(const UpdateBlockInfo &infos, TransferManagerPtr tm,

    Hpackage::PkgManager::StreamPtr &outStream, const std::vector<std::string> &lines, Uscript::UScriptContext &context)

{

    UpdateFdInfo fdInfo = CreateFdInfo(infos, tm);

    auto env = tm->GetTransferParams()->env;

    if (fdInfo.targetFd == -1 || fdInfo.sourceFd == -1) {

        LOG(ERROR) << "Failed to open block";

        fdInfo.Close();

        env->GetPkgManager()->ClosePkgStream(outStream);

        return USCRIPT_ERROR_EXECUTE;

    }

    int32_t ret = ExecuteTransferCommand(fdInfo, lines, tm, context, infos);

    SyncWriteDevPath(fdInfo.targetFd, infos.partitionName);

    fdInfo.Close();

    env->GetPkgManager()->ClosePkgStream(outStream);

    if (ret == USCRIPT_SUCCESS) {

        HandleUpdateSuccess(infos);

    }

    return ret;

}



static int32_t ExtractFileByName(Uscript::UScriptEnv &env, const std::string &fileName,

    Hpackage::PkgManager::StreamPtr &outStream, uint8_t *&outBuf, size_t &buffSize)

{

    if (env.GetPkgManager() == nullptr) {

        LOG(ERROR) << "Error to get pkg manager";

        return USCRIPT_ERROR_EXECUTE;

    }



    const FileInfo *info = env.GetPkgManager()->GetFileInfo(fileName);

    if (info == nullptr) {

        LOG(ERROR) << "GetFileInfo fail";

        return USCRIPT_ERROR_EXECUTE;

    }

    auto ret = env.GetPkgManager()->CreatePkgStream(outStream,

        fileName, info->unpackedSize, PkgStream::PkgStreamType_MemoryMap);

    if (ret != USCRIPT_SUCCESS || outStream == nullptr) {

        LOG(ERROR) << "Error to create output stream";

        return USCRIPT_ERROR_EXECUTE;

    }

    ret = env.GetPkgManager()->ExtractFile(fileName, outStream);

    if (ret != USCRIPT_SUCCESS) {

        LOG(ERROR) << "Error to extract file";

        env.GetPkgManager()->ClosePkgStream(outStream);

        return USCRIPT_ERROR_EXECUTE;

    }

    ret = outStream->GetBuffer(outBuf, buffSize);

    LOG(DEBUG) << "outBuf data size is: " << buffSize;



    return USCRIPT_SUCCESS;

}



static int32_t ExtractPatchDatFile(Uscript::UScriptEnv &env, const UpdateBlockInfo &infos,

    Hpackage::PkgManager::StreamPtr &outStream, std::string &datFile)

{

    if (env.GetPkgManager() == nullptr) {

        LOG(ERROR) << "Error to get pkg manager";

        return USCRIPT_ERROR_EXECUTE;

    }

 

    if ((!Utils::IsDirExist(GetDiffTempPath())) && (Utils::MkdirRecursive(GetDiffTempPath(), S_IRWXU) != 0)) {

        LOG(ERROR) << "Failed to make path";

        return USCRIPT_ERROR_EXECUTE;

    }

    std::string fileName = std::string(GetDiffTempPath()) + "/" + infos.patchDataName;

    auto ret = env.GetPkgManager()->CreatePkgStream(outStream, fileName, 0, PkgStream::PkgStreamType_Write);

    if (ret != USCRIPT_SUCCESS || outStream == nullptr) {

        LOG(ERROR) << "Error to create output stream";

        return USCRIPT_ERROR_EXECUTE;

    }

    ret = env.GetPkgManager()->ExtractFile(infos.patchDataName, outStream);

    if (ret != USCRIPT_SUCCESS) {

        LOG(ERROR) << "Error to extract file";

        env.GetPkgManager()->ClosePkgStream(outStream);

        return USCRIPT_ERROR_EXECUTE;

    }

    datFile = fileName;

    return USCRIPT_SUCCESS;

}



int32_t UScriptInstructionBlockUpdate::ExtractPatchFile(Uscript::UScriptEnv &env, const UpdateBlockInfo &infos,

    Hpackage::PkgManager::StreamPtr outStream, TransferParams *transferParams)

{

    if (transferParams == nullptr) {

        LOG(ERROR) << "transfer param is null";

        return USCRIPT_ERROR_EXECUTE;

    }

    LOG(INFO) << "Start unpack new data thread done. Get patch data: " << infos.patchDataName;

    transferParams->isUpdaterMode = Utils::IsUpdaterMode();

    return transferParams->isUpdaterMode ? ExtractFileByName(env, infos.patchDataName, outStream,

        transferParams->dataBuffer, transferParams->dataBufferSize) : ExtractPatchDatFile(env,

            infos, outStream, transferParams->patchDatFile);

}



int32_t UScriptInstructionBlockUpdate::ExecuteUpdateBlock(Uscript::UScriptEnv &env, Uscript::UScriptContext &context)

{

    UpdateBlockInfo infos {};

    if (GetUpdateBlockInfo(infos, env, context) != USCRIPT_SUCCESS) {

        return USCRIPT_ERROR_EXECUTE;

    }



    if (env.IsRetry()) {

        LOG(DEBUG) << "Retry updater, check if current partition updatered already during last time";

        if (PartitionRecord::GetInstance().IsPartitionUpdated(infos.partitionName)) {

            LOG(INFO) << infos.partitionName << " already updated, skip";

            return USCRIPT_SUCCESS;

        }

    }



    if (ExtractDiffPackageAndLoad(infos, env, context) != USCRIPT_SUCCESS) {

        return USCRIPT_ERROR_EXECUTE;

    }



    uint8_t *transferListBuffer = nullptr;

    size_t transferListSize = 0;

    Hpackage::PkgManager::StreamPtr outStream = nullptr;

    if (ExtractFileByName(env, infos.transferName, outStream,

                          transferListBuffer, transferListSize) != USCRIPT_SUCCESS) {

        return USCRIPT_ERROR_EXECUTE;

    }



    std::unique_ptr<TransferManager> tm = std::make_unique<TransferManager>();



    auto transferParams = tm->GetTransferParams();

    /* Save Script Env to transfer manager */

    transferParams->env = &env;



    transferParams->canWrite = true;

    std::vector<std::string> lines =

        Updater::Utils::SplitString(std::string(reinterpret_cast<const char*>(transferListBuffer)), "\n");

    // Close stream opened before.

    env.GetPkgManager()->ClosePkgStream(outStream);



    if (ExtractPatchFile(env, infos, outStream, transferParams) != USCRIPT_SUCCESS) {

        return USCRIPT_ERROR_EXECUTE;

    }



    if (InitThread(infos, tm.get()) != 0) {

        LOG(ERROR) << "Failed to create pthread";

        env.GetPkgManager()->ClosePkgStream(outStream);

        return USCRIPT_ERROR_EXECUTE;

    }

    std::vector<pid_t> tids = GetAllTids(getpid());

    env.PostMessage("set_binary_tids", VectorToString(tids));

    return DoExecuteUpdateBlock(infos, tm.get(), outStream, lines, context);

}



int32_t UScriptInstructionBlockUpdate::Execute(Uscript::UScriptEnv &env, Uscript::UScriptContext &context)

{

    int32_t result = ExecuteUpdateBlock(env, context);

    context.PushParam(result);

    return result;

}



bool UScriptInstructionBlockUpdate::GetPartitionInfo(const std::string &partition, std::string &partitionPath,

    size_t &partitionOffset) const

{

#ifdef UPDATER_USE_PTABLE

    DevicePtable &devicePtb = DevicePtable::GetInstance();

    Ptable::PtnInfo ptnInfo;

    if (!devicePtb.GetPartionInfoByName(partition, ptnInfo)) {

        return false;

    }

    partitionPath = ptnInfo.writePath;

    partitionOffset = ptnInfo.startAddr;

#endif

    return true;

}



void UScriptInstructionBlockUpdate::HandleUpdateSuccess(const UpdateBlockInfo &infos) const

{

    PartitionRecord::GetInstance().RecordPartitionUpdateStatus(infos.partitionName, true);

#ifndef UPDATER_UT

    if (!usedStashPtn_) {

        return;

    }

    std::string dataDevPath = GetStashedPath(infos);

    if (!Utils::DeleteFile(dataDevPath)) {

        LOG(WARNING) << "Failed to delete stashed partition " << dataDevPath;

    }

    std::string doneListPath;

    if (!BuildSafeStashedDevPath("done.list", doneListPath)) {

        LOG(WARNING) << "Failed to build done.list path";

        return;

    }

    std::ofstream fout(doneListPath, std::ios::app | std::ios::out);

    if (!fout.is_open()) {

        LOG(WARNING) << "Failed to open done.list";

        return;

    }

    fout << GetPartName(infos.partitionName) + std::string(IMG_SUFFIX) << "\n";

    fout.flush();

    fout.close();

    int fd = open(doneListPath.c_str(), O_RDWR);

    if (fd < 0) {

        LOG(WARNING) << "Failed to sync done.list";

    } else {

        fsync(fd);

        close(fd);

        fd = -1;

    }

    LOG(INFO) << "Update partition " << infos.partitionName << " success";

#endif

}



std::string UScriptInstructionBlockUpdate::GetStashedPath(const UpdateBlockInfo &infos) const

{

    std::string dataDevPath;

    BuildSafeStashedDevPath(GetPartName(infos.partitionName), dataDevPath);

    return dataDevPath;

}



bool UScriptInstructionBlockCheck::ExecReadBlockInfo(const std::string &devPath, Uscript::UScriptContext &context,

    time_t &mountTime, uint16_t &mountCount)

{

    UPDATER_INIT_RECORD;

    int fd = open(devPath.c_str(), O_RDWR | O_LARGEFILE | O_UNCACHE_FLAG);

    if (fd == -1) {

        LOG(ERROR) << "Failed to open file";

        UPDATER_LAST_WORD("Failed to open file", devPath);

        return false;

    }

    std::vector<uint8_t> block_buff(H_BLOCK_SIZE);

    BlockSet blk0(std::vector<BlockPair> {BlockPair{0, 1}});



    size_t pos = 0;

    std::vector<BlockPair>::iterator it = blk0.Begin();

    for (; it != blk0.End(); ++it) {

        LOG(INFO) << "BlockSet::ReadDataFromBlock lseek64";

        if (lseek64(fd, static_cast<off64_t>(it->first * H_BLOCK_SIZE), SEEK_SET) == -1) {

            LOG(ERROR) << "Failed to seek";

            close(fd);

            UPDATER_LAST_WORD(false, "Failed to seek");

            return false;

        }

        size_t size = (it->second - it->first) * H_BLOCK_SIZE;

        LOG(INFO) << "BlockSet::ReadDataFromBlock Read " << size << " from block";

        if (!Utils::ReadFully(fd, block_buff.data() + pos, size)) {

            LOG(ERROR) << "Failed to read";

            close(fd);

            UPDATER_LAST_WORD(false, "Failed to read");

            return false;

        }

        pos += size;

    }

    close(fd);

    mountTime = *reinterpret_cast<uint32_t *>(&block_buff[0x400 + 0x2C]);

    mountCount = *reinterpret_cast<uint16_t *>(&block_buff[0x400 + 0x34]);

    return true;

}



int32_t UScriptInstructionBlockCheck::Execute(Uscript::UScriptEnv &env, Uscript::UScriptContext &context)

{

    UPDATER_INIT_RECORD;

    if (context.GetParamCount() != 1) {

        LOG(ERROR) << "Invalid param";

        UPDATER_LAST_WORD(USCRIPT_INVALID_PARAM);

        return ReturnAndPushParam(USCRIPT_INVALID_PARAM, context);

    }

    if (env.IsRetry()) {

        return ReturnAndPushParam(USCRIPT_SUCCESS, context);

    }

    std::string partitionName;

    int32_t ret = context.GetParam(0, partitionName);

    if (ret != USCRIPT_SUCCESS) {

        LOG(ERROR) << "Failed to get param";

        UPDATER_LAST_WORD(USCRIPT_ERROR_EXECUTE, "Failed to get param " + partitionName);

        return ReturnAndPushParam(USCRIPT_ERROR_EXECUTE, context);

    }

    auto devPath = GetBlockDeviceByMountPoint(partitionName);

#ifndef UPDATER_UT

    if (partitionName != "/userdata") {

        std::string suffix = Utils::GetUpdateSuffix();

        devPath += suffix;

    }

#else

    devPath = "/data/updater" + partitionName;

#endif

    std::string stashedDevPath;

    if (BuildSafeStashedDevPath(partitionName, stashedDevPath) && Utils::IsFileExist(stashedDevPath)) {

        devPath = stashedDevPath;

    }

    LOG(INFO) << "UScriptInstructionBlockCheck::dev path : " << devPath;

    time_t mountTime = 0;

    uint16_t mountCount = 0;

    if (devPath.empty() || (!ExecReadBlockInfo(devPath, context, mountTime, mountCount))) {

        LOG(ERROR) << "cannot get block device of partition";

        UPDATER_LAST_WORD(USCRIPT_ERROR_EXECUTE, devPath);

        return ReturnAndPushParam(USCRIPT_ERROR_EXECUTE, context);

    }



    if (mountCount > 0) {

        std::ostringstream ostr;

        ostr << "Device was remounted R/W " << mountCount << "times\nLast remount happened on " <<

            ctime(&mountTime) << std::endl;

        std::string message = ostr.str();

        env.PostMessage("ui_log", message);

        LOG(ERROR) << message;

    }

    LOG(INFO) << "UScriptInstructionBlockCheck::Execute Success";

    context.PushParam(USCRIPT_SUCCESS);

    return USCRIPT_SUCCESS;

}



int32_t UScriptInstructionShaCheck::DoBlocksVerify(Uscript::UScriptEnv &env, const std::string &partitionName,

    const std::string &devPath)

{

    UpdateBlockInfo infos {};

    infos.partitionName = partitionName;

    infos.transferName = GetPartName(partitionName) + ".transfer.list";

    uint8_t *transferListBuffer = nullptr;

    size_t transferListSize = 0;

    Hpackage::PkgManager::StreamPtr outStream = nullptr;



    if (ExtractFileByName(env, infos.transferName, outStream,

                          transferListBuffer, transferListSize) != USCRIPT_SUCCESS) {

        LOG(ERROR) << "Error to extract " << infos.transferName;

        return USCRIPT_ERROR_EXECUTE;

    }



    std::vector<std::string> lines =

        Updater::Utils::SplitString(std::string(reinterpret_cast<const char*>(transferListBuffer)), "\n");

    // Close stream opened before.

    env.GetPkgManager()->ClosePkgStream(outStream);



    std::unique_ptr<TransferManager> tm = std::make_unique<TransferManager>();

    auto transferParams = tm->GetTransferParams();

    transferParams->env = &env;

    transferParams->canWrite = false;

    transferParams->devPath = devPath;

    transferParams->storeBase = GetDiffTempPath() + partitionName + "_tmp";

    transferParams->retryFile = GetDiffTempPath() + partitionName + "_retry";



    LOG(INFO) << "Store base path is " << transferParams->storeBase;

    transferParams->storeCreated = Store::CreateNewSpace(transferParams->storeBase, !transferParams->env->IsRetry());

    if (transferParams->storeCreated == -1) {

        LOG(ERROR) << "Error to create new store space";

        return USCRIPT_ERROR_EXECUTE;

    }

    int fd = open(devPath.c_str(), O_RDWR | O_LARGEFILE | O_UNCACHE_FLAG);

    if (fd == -1) {

        LOG(ERROR) << "Failed to open block";

        return USCRIPT_ERROR_EXECUTE;

    }

    if (!tm->CommandsParser(fd, fd, lines)) {

        close(fd);

        LOG(ERROR) << "Failed to block verify";

        return USCRIPT_ERROR_EXECUTE;

    }

    if (transferParams->storeCreated != -1) {

        Store::DoFreeSpace(transferParams->storeBase);

    }

    close(fd);

    return USCRIPT_SUCCESS;

}



bool UScriptInstructionShaCheck::IsTargetShaDiff(const std::string &devPath, const ShaInfo &shaInfo)

{

    std::string tgtResultSha = CalculateBlockSha(devPath, shaInfo.targetPairs);

    if (tgtResultSha.empty()) {

        LOG(WARNING) << "target sha is empty";

        return true;

    }

    LOG(INFO) << "tgtResultSha: " << tgtResultSha << ", shaInfo.targetSha: " << shaInfo.targetSha;

    return (tgtResultSha != shaInfo.targetSha);

}



int UScriptInstructionShaCheck::ExecReadShaInfo(Uscript::UScriptEnv &env, const std::string &devPath,

    const ShaInfo &shaInfo, const std::string &partitionName)

{

    UPDATER_INIT_RECORD;

    std::string resultSha = CalculateBlockSha(devPath, shaInfo.blockPairs);

    if (resultSha != shaInfo.contrastSha && IsTargetShaDiff(devPath, shaInfo)) {

        if (DoBlocksVerify(env, partitionName, devPath) != USCRIPT_SUCCESS) {

            LOG(ERROR) << "Different sha256, cannot continue";

            LOG(ERROR) << "blockPairs:" << shaInfo.blockPairs;

            LOG(ERROR) << "resultSha: " << resultSha << ", shaInfo.contrastSha: " << shaInfo.contrastSha;

            PrintAbnormalBlockHash(devPath, shaInfo.blockPairs);

            UPDATER_LAST_WORD(devPath.substr(devPath.find_last_of("/") + 1), USCRIPT_ERROR_EXECUTE);

            env.PostMessage(UPDATER_RETRY_TAG, VERIFY_FAILED_REBOOT);

            return USCRIPT_ERROR_EXECUTE;

        }

    }

    LOG(INFO) << "UScriptInstructionShaCheck::Execute Success";

    return USCRIPT_SUCCESS;

}



void UScriptInstructionShaCheck::PrintAbnormalBlockHash(const std::string &devPath, const std::string &blockPairs)

{

    int fd = open(devPath.c_str(), O_RDWR | O_LARGEFILE | O_UNCACHE_FLAG);

    if (fd == -1) {

        LOG(ERROR) << "Failed to open file " << devPath;

        return;

    }

    const bool isUpdaterMode = Utils::IsUpdaterMode();

    int64_t cycleCount = 0;

    BlockSet blk;

    blk.ParserAndInsert(blockPairs);

    std::vector<uint8_t> block_buff(H_BLOCK_SIZE);

    std::vector<BlockPair>::iterator it = blk.Begin();

    for (; it != blk.End(); ++it) {

        if (lseek64(fd, static_cast<off64_t>(it->first * H_BLOCK_SIZE), SEEK_SET) == -1) {

            LOG(ERROR) << "Failed to seek";

            close(fd);

            return;

        }

        SHA256_CTX ctx;

        SHA256_Init(&ctx);

        for (size_t i = it->first; i < it->second; ++i) {

            if (!Utils::ReadFully(fd, block_buff.data(), H_BLOCK_SIZE)) {

                LOG(ERROR) << "Failed to read";

                close(fd);

                return;

            }

            SHA256_Update(&ctx, block_buff.data(), H_BLOCK_SIZE);

            ++cycleCount;

            if (!isUpdaterMode && cycleCount % SHA_DELAY_CYCLE_COUNT == 0) {

                std::this_thread::sleep_for(std::chrono::milliseconds(SHA_DELAY_MILLIS_SECOND));

            }

        }

        uint8_t digest[SHA256_DIGEST_LENGTH] = {0};

        SHA256_Final(digest, &ctx);

        LOG(ERROR) << "block id:" << it->first << "-" << it->second <<

            " hex:" << Utils::ConvertSha256Hex(digest, SHA256_DIGEST_LENGTH);

    }

    close(fd);

}



std::string UScriptInstructionShaCheck::CalculateBlockSha(const std::string &devPath, const std::string &blockPairs)

{

    UPDATER_INIT_RECORD;

    if (blockPairs.empty()) {

        LOG(ERROR) << "Failed to get blockPairs";

        return "";

    }



    int fd = open(devPath.c_str(), O_RDWR | O_LARGEFILE | O_UNCACHE_FLAG);

    if (fd == -1) {

        LOG(ERROR) << "Failed to open file";

        UPDATER_LAST_WORD(USCRIPT_ERROR_EXECUTE, devPath);

        return "";

    }

    const bool isUpdaterMode = Utils::IsUpdaterMode();

    int64_t cycleCount = 0;

    BlockSet blk;

    blk.ParserAndInsert(blockPairs);

    std::vector<uint8_t> block_buff(H_BLOCK_SIZE);

    SHA256_CTX ctx;

    SHA256_Init(&ctx);

    std::vector<BlockPair>::iterator it = blk.Begin();

    for (; it != blk.End(); ++it) {

        if (lseek64(fd, static_cast<off64_t>(it->first * H_BLOCK_SIZE), SEEK_SET) == -1) {

            LOG(ERROR) << "Failed to seek";

            close(fd);

            UPDATER_LAST_WORD(USCRIPT_ERROR_EXECUTE, "Failed to seek");

            return "";

        }

        for (size_t i = it->first; i < it->second; ++i) {

            if (!Utils::ReadFully(fd, block_buff.data(), H_BLOCK_SIZE)) {

                LOG(ERROR) << "Failed to read";

                close(fd);

                UPDATER_LAST_WORD(USCRIPT_ERROR_EXECUTE, "Failed to read");

                return "";

            }

            SHA256_Update(&ctx, block_buff.data(), H_BLOCK_SIZE);

            ++cycleCount;

            if (!isUpdaterMode && cycleCount % SHA_DELAY_CYCLE_COUNT == 0) {

                std::this_thread::sleep_for(std::chrono::milliseconds(SHA_DELAY_MILLIS_SECOND));

            }

        }

    }

    close(fd);



    uint8_t digest[SHA256_DIGEST_LENGTH] = {0};

    SHA256_Final(digest, &ctx);

    return Utils::ConvertSha256Hex(digest, SHA256_DIGEST_LENGTH);

}



int32_t UScriptInstructionShaCheck::SetShaInfo(Uscript::UScriptContext &context, ShaInfo &shaInfo)

{

    UPDATER_INIT_RECORD;

    int32_t ret = context.GetParam(1, shaInfo.blockPairs);

    if (ret != USCRIPT_SUCCESS) {

        LOG(ERROR) << "Failed to get param blockPairs";

        UPDATER_LAST_WORD(ret, "Failed to get param blockPairs");

        return USCRIPT_ERROR_EXECUTE;

    }



    ret = context.GetParam(SHA_CHECK_SECOND, shaInfo.contrastSha);

    if (ret != USCRIPT_SUCCESS) {

        LOG(ERROR) << "Failed to get param contrastSha";

        UPDATER_LAST_WORD(ret, "Failed to get param contrastSha");

        return USCRIPT_ERROR_EXECUTE;

    }



    // Only three parameters can be obtained for the upgrade package of an earlier version.

    ret = context.GetParam(SHA_CHECK_TARGETPAIRS_INDEX, shaInfo.targetPairs);

    if (ret != USCRIPT_SUCCESS) {

        LOG(WARNING) << "Failed to get param targetPairs";

    }



    ret = context.GetParam(SHA_CHECK_TARGETSHA_INDEX, shaInfo.targetSha);

    if (ret != USCRIPT_SUCCESS) {

        LOG(WARNING) << "Failed to get param targetSha";

    }



    return USCRIPT_SUCCESS;

}



int32_t UScriptInstructionShaCheck::Execute(Uscript::UScriptEnv &env, Uscript::UScriptContext &context)

{

    UPDATER_INIT_RECORD;

    int32_t paramCount = context.GetParamCount();

    if (paramCount != SHA_CHECK_PARAMS && paramCount != SHA_CHECK_TARGET_PARAMS) {

        LOG(ERROR) << "Invalid param";

        UPDATER_LAST_WORD(USCRIPT_INVALID_PARAM, "Invalid param");

        return ReturnAndPushParam(USCRIPT_INVALID_PARAM, context);

    }

    if (env.IsRetry() && Utils::IsUpdaterMode() && !Utils::CheckFaultInfo(VERIFY_FAILED_REBOOT)) {

        return ReturnAndPushParam(USCRIPT_SUCCESS, context);

    }



    std::string partitionName;

    int32_t ret = context.GetParam(0, partitionName);

    if (ret != USCRIPT_SUCCESS) {

        LOG(ERROR) << "Failed to get param";

        UPDATER_LAST_WORD(USCRIPT_ERROR_EXECUTE, "Failed to get param");

        return ReturnAndPushParam(USCRIPT_ERROR_EXECUTE, context);

    }



    ShaInfo shaInfo {};

    ret = SetShaInfo(context, shaInfo);

    if (ret != USCRIPT_SUCCESS) {

        LOG(ERROR) << "Failed to set sha info";

        return ReturnAndPushParam(ret, context);

    }



    auto devPath = GetBlockDeviceByMountPoint(partitionName);

    LOG(INFO) << "UScriptInstructionShaCheck::dev path : " << devPath;

    if (devPath.empty()) {

        LOG(ERROR) << "cannot get block device of partition";

        UPDATER_LAST_WORD(USCRIPT_ERROR_EXECUTE, "cannot get block device of partition");

        return ReturnAndPushParam(USCRIPT_ERROR_EXECUTE, context);

    }

#ifndef UPDATER_UT

    if (partitionName != "/userdata") {

        std::string suffix = Utils::GetUpdateSuffix();

        devPath += suffix;

    }

#else

    devPath = "/data/updater" + partitionName;

#endif

    std::string stashedDevPath;

    if (BuildSafeStashedDevPath(partitionName, stashedDevPath) && Utils::IsFileExist(stashedDevPath)) {

        devPath = stashedDevPath;

    }

    LOG(INFO) << "write partition path: " << devPath;

    ret = ExecReadShaInfo(env, devPath, shaInfo, partitionName);

    return ReturnAndPushParam(ret, context);

}

}