* 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 "command_process.h"
#include <cstdio>
#include <fcntl.h>
#include <linux/fs.h>
#include <memory>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "applypatch/block_set.h"
#include "applypatch/block_writer.h"
#include "applypatch/data_writer.h"
#include "applypatch/store.h"
#include "applypatch/transfer_manager.h"
#include "log/log.h"
#include "securec.h"
#include "utils.h"
using namespace Hpackage;
using namespace Updater::Utils;
namespace Updater {
CommandResult AbortCommandFn::Execute(const Command ¶ms)
{
return SUCCESS;
}
CommandResult NewCommandFn::Execute(const Command ¶ms)
{
if (params.IsStreamCmd()) {
return (StreamExecute(params) != 0) ? FAILED : SUCCESS;
}
BlockSet bs;
bs.ParserAndInsert(params.GetArgumentByPos(1));
LOG(DEBUG) << " writing " << bs.TotalBlockSize() << " blocks of new data";
auto writerThreadInfo = params.GetTransferParams()->writerThreadInfo.get();
pthread_mutex_lock(&writerThreadInfo->mutex);
size_t offset = params.GetTransferParams()->offset;
writerThreadInfo->writer = std::make_unique<BlockWriter>(params.GetTargetFileDescriptor(), offset, bs);
pthread_cond_broadcast(&writerThreadInfo->cond);
while (writerThreadInfo->writer != nullptr) {
LOG(DEBUG) << "wait for new data write done...";
if (!writerThreadInfo->readyToWrite) {
LOG(ERROR) << "writer thread could not write blocks. " << bs.TotalBlockSize() * H_BLOCK_SIZE -
writerThreadInfo->writer->GetTotalWritten() << " bytes lost";
pthread_mutex_unlock(&writerThreadInfo->mutex);
writerThreadInfo->writer.reset();
writerThreadInfo->writer = nullptr;
return FAILED;
}
LOG(DEBUG) << "Writer already written " << writerThreadInfo->writer->GetTotalWritten() << " byte(s)";
pthread_cond_wait(&writerThreadInfo->cond, &writerThreadInfo->mutex);
}
pthread_mutex_unlock(&writerThreadInfo->mutex);
LOG(DEBUG) << " writing " << bs.TotalBlockSize() << " blocks of new data end";
writerThreadInfo->writer.reset();
params.GetTransferParams()->written += bs.TotalBlockSize();
return SUCCESS;
}
int32_t NewCommandFn::StreamExecute(const Command ¶ms)
{
size_t pos = H_NEW_CMD_ARGS_START;
uint8_t *addr = params.GetTransferParams()->dataBuffer;
size_t size = params.GetTransferParams()->dataBufferSize;
size_t offset = params.GetTransferParams()->offset;
std::string tgtHash = "";
tgtHash = params.GetArgumentByPos(pos++);
BlockSet bs(params.GetArgumentByPos(pos++), offset);
std::unique_ptr<BlockWriter> writer = std::make_unique<BlockWriter>(params.GetTargetFileDescriptor(),
offset, bs);
while (size > 0) {
size_t toWrite = std::min(size, writer->GetBlocksSize() - writer->GetTotalWritten());
if (toWrite == 0) {
break;
}
bool ret = writer->Write(addr, toWrite, nullptr);
if (!ret) {
return -1;
}
size -= toWrite;
addr += toWrite;
}
size_t tgtBlockSize = bs.TotalBlockSize() * H_BLOCK_SIZE;
std::vector<uint8_t> tgtBuffer(std::min(tgtBlockSize, params.GetTransferParams()->dataBufferSize));
if (bs.ReadDataFromBlock(params.GetTargetFileDescriptor(), tgtBuffer) == 0) {
LOG(ERROR) << "Read data from block error, tgtBuffer size: " << tgtBuffer.size();
return -1;
}
std::string hexDigest;
if (bs.VerifySha256(tgtBuffer, bs.TotalBlockSize(), tgtHash, hexDigest) == 0) {
LOG(ERROR) << "Will write same sha256 blocks to target, no need to write";
return -1;
}
std::vector<uint8_t>().swap(tgtBuffer);
return 0;
}
CommandResult ZeroAndEraseCommandFn::Execute(const Command ¶ms)
{
bool isErase = false;
if (params.GetCommandType() == CommandType::ERASE) {
isErase = true;
LOG(INFO) << "Start run ERASE command";
}
if (isErase && Utils::IsUpdaterMode()) {
struct stat statBlock {};
if (fstat(params.GetTargetFileDescriptor(), &statBlock) == -1) {
LOG(ERROR) << "Failed to fstat";
return FAILED;
}
#ifndef UPDATER_UT
if (!S_ISBLK(statBlock.st_mode)) {
LOG(ERROR) << "Invalid block device";
return FAILED;
}
#endif
}
size_t offset = params.GetTransferParams()->offset;
BlockSet blk(offset);
blk.ParserAndInsert(params.GetArgumentByPos(1));
LOG(INFO) << "Parser params to block set";
auto ret = CommandResult(blk.WriteZeroToBlock(params.GetTargetFileDescriptor(), isErase));
if (ret == SUCCESS && !isErase) {
params.GetTransferParams()->written += blk.TotalBlockSize();
}
return ret;
}
bool LoadTarget(const Command ¶ms, size_t &pos, std::vector<uint8_t> &buffer,
BlockSet &targetBlock, CommandResult &result)
{
CommandType type = params.GetCommandType();
std::string srcHash = "";
std::string tgtHash = "";
if (type == CommandType::MOVE) {
srcHash = params.GetArgumentByPos(pos++);
tgtHash = srcHash;
} else if (type != CommandType::COPY) {
srcHash = params.GetArgumentByPos(pos++);
tgtHash = params.GetArgumentByPos(pos++);
}
std::string cmdTmp = params.GetArgumentByPos(pos++);
targetBlock.ParserAndInsert(cmdTmp);
if (type != CommandType::COPY) {
size_t tgtBlockSize = targetBlock.TotalBlockSize() * H_BLOCK_SIZE;
std::vector<uint8_t> tgtBuffer(tgtBlockSize);
if (targetBlock.ReadDataFromBlock(params.GetTargetFileDescriptor(), tgtBuffer) == 0) {
LOG(ERROR) << "Read data from block error, TotalBlockSize: " << targetBlock.TotalBlockSize();
result = FAILED;
return false;
}
std::string hexDigest;
if (targetBlock.VerifySha256(tgtBuffer, targetBlock.TotalBlockSize(), tgtHash, hexDigest) == 0) {
result = SUCCESS;
return false;
}
std::vector<uint8_t>().swap(tgtBuffer);
}
std::string blockLen = params.GetArgumentByPos(pos++);
size_t srcBlockSize = String2Int<size_t>(blockLen, N_DEC);
buffer.resize(srcBlockSize * H_BLOCK_SIZE);
if (auto ret = targetBlock.LoadTargetBuffer(params, buffer, srcBlockSize, pos, srcHash); ret != 0) {
LOG(ERROR) << "Failed to load blocks " << ret;
result = FAILED;
return false;
}
result = SUCCESS;
return true;
}
int32_t DiffAndMoveCommandFn::WriteDiffToBlock(const Command ¶ms, std::vector<uint8_t> &srcBuffer,
uint8_t *patchBuffer, size_t patchLength, BlockSet &targetBlock)
{
CommandType type = params.GetCommandType();
return targetBlock.WriteDiffToBlock(params, srcBuffer, patchBuffer, patchLength, type == CommandType::IMGDIFF);
}
int32_t DiffAndMoveCommandFn::WriteFileToBlock(const Command ¶ms, std::vector<uint8_t> &srcBuffer,
size_t offset, size_t patchLength, BlockSet &targetBlock)
{
std::ifstream fin(params.GetTransferParams()->patchDatFile, std::ios::in | std::ios::binary);
if (!fin.is_open()) {
LOG(ERROR) << "open dat file failed " << params.GetTransferParams()->patchDatFile;
return static_cast<int>(FAILED);
}
std::unique_ptr<uint8_t[]> patchBuffer = std::make_unique<uint8_t[]>(patchLength);
(void)memset_s(patchBuffer.get(), patchLength, 0, patchLength);
if ((!fin.seekg(static_cast<int64_t>(offset), std::ios::beg)) ||
(!fin.read(reinterpret_cast<char *>(patchBuffer.get()), patchLength))) {
LOG(ERROR) << "read dat file failed gcount " << fin.gcount() << ", patch len " << patchLength;
fin.close();
return static_cast<int>(FAILED);
}
fin.close();
return WriteDiffToBlock(params, srcBuffer, patchBuffer.get(), patchLength, targetBlock);
}
CommandResult DiffAndMoveCommandFn::Execute(const Command ¶ms)
{
CommandType type = params.GetCommandType();
size_t pos = H_DIFF_CMD_ARGS_START;
if (type == CommandType::MOVE) {
pos = H_MOVE_CMD_ARGS_START;
} else if (type == CommandType::COPY) {
pos = H_COPY_CMD_ARGS_START;
}
if (SkipInPlaceMove(params, type)) {
return SUCCESS;
}
BlockSet targetBlock(params.GetTransferParams()->offset);
std::vector<uint8_t> buffer;
CommandResult result = FAILED;
if (!LoadTarget(params, pos, buffer, targetBlock, result) || !params.GetTransferParams()->canWrite) {
return result;
}
int32_t ret = -1;
if (type != CommandType::MOVE && type != CommandType::COPY) {
pos = H_MOVE_CMD_ARGS_START;
size_t offset = params.IsStreamCmd() ? 0 :
Utils::String2Int<size_t>(params.GetArgumentByPos(pos), Utils::N_DEC);
pos++;
size_t patchLength = Utils::String2Int<size_t>(params.GetArgumentByPos(pos++), Utils::N_DEC);
if (params.GetTransferParams()->isUpdaterMode || params.IsStreamCmd()) {
uint8_t *patchBuffer = params.GetTransferParams()->dataBuffer + offset;
ret = WriteDiffToBlock(params, buffer, patchBuffer, patchLength, targetBlock);
} else {
ret = WriteFileToBlock(params, buffer, offset, patchLength, targetBlock);
}
} else {
ret = targetBlock.WriteDataToBlock(params.GetTargetFileDescriptor(), buffer) == 0 ? -1 : 0;
}
if (ret != 0) {
LOG(ERROR) << "fail to write block data.";
return errno == EIO ? NEED_RETRY : FAILED;
}
std::string storeBase = params.GetTransferParams()->storeBase;
std::string freeStash = params.GetTransferParams()->freeStash;
if (!freeStash.empty()) {
if (Store::FreeStore(storeBase, freeStash) != 0) {
LOG(WARNING) << "fail to delete file: " << freeStash;
}
params.GetTransferParams()->freeStash.clear();
}
params.GetTransferParams()->written += targetBlock.TotalBlockSize();
return SUCCESS;
}
bool DiffAndMoveCommandFn::SkipInPlaceMove(const Command ¶ms, CommandType type) const
{
return params.GetTransferParams()->canWrite && type == CommandType::MOVE &&
params.GetArgumentByPos(H_MOVE_CMD_SRC_START) == params.GetArgumentByPos(H_MOVE_CMD_TGT_START) &&
params.GetTransferParams()->inPlaceDiff;
}
CommandResult FreeCommandFn::Execute(const Command ¶ms)
{
if (!IsUpdaterMode() && params.IsStreamCmd()) {
return SUCCESS;
}
std::string shaStr = params.GetArgumentByPos(1);
std::string storeBase = params.GetTransferParams()->storeBase;
if (params.GetTransferParams()->storeCreated == 0) {
return CommandResult(Store::FreeStore(storeBase, shaStr));
}
return SUCCESS;
}
CommandResult StashCommandFn::Execute(const Command ¶ms)
{
if (!IsUpdaterMode() && params.IsStreamCmd()) {
return SUCCESS;
}
size_t pos = 1;
const std::string shaStr = params.GetArgumentByPos(pos++);
BlockSet srcBlk;
LOG(DEBUG) << "Get source block info to block set";
srcBlk.ParserAndInsert(params.GetArgumentByPos(pos++));
size_t srcBlockSize = srcBlk.TotalBlockSize();
std::vector<uint8_t> buffer;
buffer.resize(srcBlockSize * H_BLOCK_SIZE);
std::string storeBase = params.GetTransferParams()->storeBase;
LOG(DEBUG) << "Confirm whether the block is stored";
if (Store::LoadDataFromStore(storeBase, shaStr, buffer) == 0) {
LOG(INFO) << "The stash has been stored, skipped";
return SUCCESS;
}
LOG(DEBUG) << "Read block data to buffer";
if (srcBlk.ReadDataFromBlock(params.GetSrcFileDescriptor(), buffer) == 0) {
LOG(ERROR) << "Error to load block data";
return FAILED;
}
std::string hexDigest;
int32_t res = srcBlk.VerifySha256(buffer, srcBlockSize, shaStr, hexDigest);
if (res != 0 && !params.GetTransferParams()->canWrite) {
res = BlockVerify(params, buffer, srcBlockSize, shaStr, pos);
}
if (res != 0) {
LOG(WARNING) << "failed to load source blocks for stash";
return SUCCESS;
}
LOG(DEBUG) << "store " << srcBlockSize << " blocks to " << storeBase << "/" << shaStr;
int ret = Store::WriteDataToStore(storeBase, shaStr, buffer, srcBlockSize * H_BLOCK_SIZE);
return CommandResult(ret);
}
}