* 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 "applypatch/block_set.h"
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <openssl/sha.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "applypatch/command.h"
#include "applypatch/store.h"
#include "applypatch/transfer_manager.h"
#include "log/dump.h"
#include "log/log.h"
#include "patch/update_patch.h"
#include "securec.h"
#include "utils.h"
using namespace Updater;
using namespace Updater::Utils;
namespace Updater {
BlockSet::BlockSet()
{
blockSize_ = 0;
offset_ = 0;
}
BlockSet::BlockSet(size_t offset)
{
blockSize_ = 0;
offset_ = offset;
}
BlockSet::BlockSet(std::vector<BlockPair> &&pairs)
{
blockSize_ = 0;
offset_ = 0;
if (pairs.empty()) {
LOG(ERROR) << "Invalid block.";
return;
}
for (const auto &pair : pairs) {
if (!CheckReliablePair(pair)) {
return;
}
PushBack(pair);
}
}
BlockSet::BlockSet(const std::string &blockStr, size_t offset)
{
blockSize_ = 0;
ParserAndInsert(blockStr);
offset_ = offset;
}
bool BlockSet::CheckReliablePair(BlockPair pair)
{
if (pair.first >= pair.second) {
LOG(ERROR) << "Invalid number of block size";
return false;
}
size_t size = pair.second - pair.first;
if (blockSize_ >= (SIZE_MAX - size)) {
LOG(ERROR) << "Block size overflow";
return false;
}
return true;
}
void BlockSet::PushBack(BlockPair blockPair)
{
blocks_.push_back(std::move(blockPair));
blockSize_ += (blockPair.second - blockPair.first);
}
void BlockSet::ClearBlocks()
{
blockSize_ = 0;
blocks_.clear();
}
bool BlockSet::ParserAndInsert(const std::string &blockStr)
{
if (blockStr == "") {
LOG(ERROR) << "Invalid argument, this argument is empty";
return false;
}
std::vector<std::string> pairs = SplitString(blockStr, ",");
return ParserAndInsert(pairs);
}
bool BlockSet::ParserAndInsert(const std::vector<std::string> &blockToken)
{
ClearBlocks();
if (blockToken.empty()) {
LOG(ERROR) << "Invalid block token argument";
return false;
}
if (blockToken.size() < 3) {
LOG(ERROR) << "Too small blocks_ in argument";
return false;
}
unsigned long long int blockPairSize;
std::vector<std::string> bt = blockToken;
std::vector<std::string>::iterator bp = bt.begin();
blockPairSize = String2Int<unsigned long long int>(*bp, N_DEC);
if (blockPairSize == 0 || blockPairSize % 2 != 0 ||
blockPairSize != bt.size() - 1) {
LOG(ERROR) << "Invalid number in block token";
return false;
}
while (++bp != bt.end()) {
size_t first = String2Int<size_t>(*bp++, N_DEC);
size_t second = String2Int<size_t>(*bp, N_DEC);
blocks_.push_back(BlockPair {
first, second
});
blockSize_ += (second - first);
}
return true;
}
std::vector<BlockPair>::iterator BlockSet::Begin()
{
return blocks_.begin();
}
std::vector<BlockPair>::iterator BlockSet::End()
{
return blocks_.end();
}
std::vector<BlockPair>::const_iterator BlockSet::CBegin() const
{
return blocks_.cbegin();
}
std::vector<BlockPair>::const_iterator BlockSet::CEnd() const
{
return blocks_.cend();
}
std::vector<BlockPair>::const_reverse_iterator BlockSet::CrBegin() const
{
return blocks_.crbegin();
}
std::vector<BlockPair>::const_reverse_iterator BlockSet::CrEnd() const
{
return blocks_.crend();
}
size_t BlockSet::ReadDataFromBlock(int fd, std::vector<uint8_t> &buffer)
{
size_t pos = 0;
std::vector<BlockPair>::iterator it = blocks_.begin();
int ret;
for (; it != blocks_.end(); ++it) {
ret = lseek64(fd, static_cast<off64_t>(it->first * H_BLOCK_SIZE + offset_), SEEK_SET);
if (ret == -1) {
LOG(ERROR) << "Fail to seek";
return 0;
}
size_t size = std::min(buffer.size() - pos, (it->second - it->first) * H_BLOCK_SIZE);
if (!Utils::ReadFully(fd, buffer.data() + pos, size)) {
LOG(ERROR) << "Fail to read " << size << " " << offset_;
return 0;
}
pos += size;
}
if (pos == 0) {
LOG(ERROR) << "Read data from block failed " << blocks_.size();
}
return pos;
}
size_t BlockSet::WriteDataToBlock(int fd, std::vector<uint8_t> &buffer)
{
size_t pos = 0;
std::vector<BlockPair>::iterator it = blocks_.begin();
int ret = 0;
for (; it != blocks_.end(); ++it) {
off64_t offset = static_cast<off64_t>(it->first * H_BLOCK_SIZE + offset_);
size_t writeSize = (it->second - it->first) * H_BLOCK_SIZE;
ret = lseek64(fd, offset, SEEK_SET);
if (ret == -1) {
LOG(ERROR) << "BlockSet::WriteDataToBlock Fail to seek";
return 0;
}
if (Utils::WriteFully(fd, buffer.data() + pos, writeSize) == false) {
LOG(ERROR) << "Write data to block error, errno : " << errno;
return 0;
}
pos += writeSize;
}
if (fsync(fd) == -1) {
LOG(ERROR) << "Failed to fsync" << strerror(errno);
return 0;
}
return pos;
}
size_t BlockSet::CountOfRanges() const
{
return blocks_.size();
}
size_t BlockSet::TotalBlockSize() const
{
return blockSize_;
}
int32_t BlockSet::VerifySha256(const std::vector<uint8_t> &buffer, const size_t size,
const std::string &expected, std::string &hexDigest)
{
UPDATER_INIT_RECORD;
uint8_t digest[SHA256_DIGEST_LENGTH];
SHA256(buffer.data(), size * H_BLOCK_SIZE, digest);
hexDigest = Utils::ConvertSha256Hex(digest, SHA256_DIGEST_LENGTH);
if (hexDigest == expected) {
return 0;
}
return -1;
}
bool BlockSet::IsTwoBlocksOverlap(const BlockSet &source, BlockSet &target)
{
auto firstIter = source.CBegin();
for (; firstIter != source.CEnd(); ++firstIter) {
std::vector<BlockPair>::iterator secondIter = target.Begin();
for (; secondIter != target.End(); ++secondIter) {
if (!(secondIter->first >= firstIter->second ||
firstIter->first >= secondIter->second)) {
return true;
}
}
}
return false;
}
void BlockSet::MoveBlock(std::vector<uint8_t> &target, const BlockSet& locations,
const std::vector<uint8_t>& source)
{
const uint8_t *sd = source.data();
uint8_t *td = target.data();
size_t start = locations.TotalBlockSize();
for (auto it = locations.CrBegin(); it != locations.CrEnd(); it++) {
size_t blocks = it->second - it->first;
start -= blocks;
size_t remainSize = blocks * H_BLOCK_SIZE;
size_t offset = 0;
while (remainSize > 0) {
size_t moveSize = (remainSize > SECUREC_MEM_MAX_LEN) ? SECUREC_MEM_MAX_LEN : remainSize;
if (memmove_s(td + (it->first * H_BLOCK_SIZE) + offset, moveSize,
sd + (start * H_BLOCK_SIZE) + offset, moveSize) != EOK) {
LOG(ERROR) << "MoveBlock memmove_s failed: " << errno;
return;
}
remainSize -= moveSize;
offset += moveSize;
}
}
}
int32_t BlockSet::LoadStreamStashBuffer(const Command &cmd, const std::string &storeBase, std::vector<uint8_t> &stash,
const std::vector<std::string> &stashTokens, const std::vector<std::string> &tokens)
{
BlockSet srcBlk;
if (IsUpdaterMode()) {
auto ret = Store::LoadDataFromStore(storeBase, tokens[H_ZERO_NUMBER], stash);
if (ret == -1) {
LOG(ERROR) << "Failed to load tokens";
return -1;
}
return 0;
}
if (stashTokens.size() <= H_ONE_NUMBER) {
LOG(ERROR) << "Invalid stash tokens, size: " << stashTokens.size();
return -1;
}
srcBlk.ParserAndInsert(stashTokens[H_ONE_NUMBER]);
stash.resize(srcBlk.TotalBlockSize() * H_BLOCK_SIZE);
if (srcBlk.ReadDataFromBlock(cmd.GetSrcFileDescriptor(), stash) == 0) {
LOG(ERROR) << "ReadDataFromBlock failed";
return -1;
}
return 0;
}
int32_t BlockSet::LoadStashBuffer(const Command &cmd, size_t &pos, const std::string &storeBase,
std::vector<uint8_t> &sourceBuffer)
{
std::string lastArg = cmd.GetArgumentByPos(pos++);
while (lastArg != "") {
std::vector<std::string> tokens = SplitString(lastArg, ":");
if (tokens.size() != H_CMD_ARGS_LIMIT) {
LOG(ERROR) << "invalid parameter";
return -1;
}
if (!cmd.IsStreamCmd() || tokens[1].find("-") == std::string::npos) {
std::vector<uint8_t> stash;
auto ret = Store::LoadDataFromStore(storeBase, tokens[H_ZERO_NUMBER], stash);
if (ret != 0) {
LOG(ERROR) << "Failed to load tokens, ret = " << ret;
return ret;
}
BlockSet locations;
locations.ParserAndInsert(tokens[1]);
MoveBlock(sourceBuffer, locations, stash);
lastArg = cmd.GetArgumentByPos(pos++);
} else {
std::vector<std::string> stashTokens = SplitString(tokens[1], "-");
std::vector<uint8_t> stash;
if (LoadStreamStashBuffer(cmd, storeBase, stash, stashTokens, tokens) == -1) {
return -1;
}
BlockSet locations;
locations.ParserAndInsert(stashTokens[H_ZERO_NUMBER]);
MoveBlock(sourceBuffer, locations, stash);
lastArg = cmd.GetArgumentByPos(pos++);
}
}
return 1;
}
int32_t BlockSet::LoadSourceBuffer(const Command &cmd, size_t &pos, std::vector<uint8_t> &sourceBuffer,
bool &isOverlap, size_t &srcBlockSize)
{
std::string targetCmd = cmd.GetArgumentByPos(pos++);
std::string storeBase = cmd.GetTransferParams()->storeBase;
if (targetCmd != "-") {
BlockSet srcBlk;
srcBlk.ParserAndInsert(targetCmd);
if (!cmd.IsStreamCmd()) {
isOverlap = IsTwoBlocksOverlap(srcBlk, *this);
}
if (srcBlk.ReadDataFromBlock(cmd.GetSrcFileDescriptor(), sourceBuffer) == 0) {
LOG(ERROR) << "ReadDataFromBlock failed";
return -1;
}
std::string nextArgv = cmd.GetArgumentByPos(pos++);
if (nextArgv == "") {
return 1;
}
BlockSet locations;
locations.ParserAndInsert(nextArgv);
MoveBlock(sourceBuffer, locations, sourceBuffer);
}
return LoadStashBuffer(cmd, pos, storeBase, sourceBuffer);
}
__attribute__((weak)) int32_t BlockVerify(const Command &cmd, std::vector<uint8_t> &buffer,
const size_t size, const std::string srcHash, size_t &pos)
{
return -1;
}
int32_t BlockSet::LoadTargetBuffer(const Command &cmd, std::vector<uint8_t> &buffer, size_t &blockSize,
size_t pos, std::string &srcHash)
{
bool isOverlap = false;
auto ret = LoadSourceBuffer(cmd, pos, buffer, isOverlap, blockSize);
if (ret != 1) {
LOG(ERROR) << "LoadSourceBuffer failed, ret is " << ret;
return ret;
}
if (cmd.IsStreamCmd()) {
return 0;
}
std::string storeBase = cmd.GetTransferParams()->storeBase;
std::string storePath = storeBase + "/" + srcHash;
struct stat storeStat {};
int res = stat(storePath.c_str(), &storeStat);
std::string hexDigest;
int32_t verifyRes = VerifySha256(buffer, blockSize, srcHash, hexDigest);
if (verifyRes != 0 && !cmd.GetTransferParams()->canWrite) {
return BlockVerify(cmd, buffer, blockSize, srcHash, pos);
}
if (verifyRes == 0) {
if (isOverlap && res != 0) {
cmd.GetTransferParams()->freeStash = srcHash;
ret = Store::WriteDataToStore(storeBase, srcHash, buffer, blockSize * H_BLOCK_SIZE);
if (ret != 0) {
LOG(ERROR) << "failed to stash overlapping source blocks";
return -1;
}
}
return 0;
}
ret = Store::LoadDataFromStore(storeBase, srcHash, buffer);
if (ret == 0) {
return 0;
}
LOG(ERROR) << "LoadDataFromStore failed, ret is " << ret;
return -1;
}
int32_t BlockSet::WriteZeroToBlock(int fd, bool isErase)
{
std::vector<uint8_t> buffer;
buffer.resize(H_BLOCK_SIZE);
if (memset_s(buffer.data(), H_BLOCK_SIZE, 0, H_BLOCK_SIZE) != EOK) {
LOG(ERROR) << "memset_s failed";
return -1;
}
auto iter = blocks_.begin();
while (iter != blocks_.end()) {
off64_t offset = static_cast<off64_t>(iter->first * H_BLOCK_SIZE + offset_);
int ret = 0;
if (isErase && Utils::IsUpdaterMode()) {
#ifndef UPDATER_UT
size_t writeSize = (iter->second - iter->first) * H_BLOCK_SIZE;
uint64_t arguments[2] = {static_cast<uint64_t>(offset), writeSize};
ret = ioctl(fd, BLKDISCARD, &arguments);
if (ret == -1 && errno != EOPNOTSUPP) {
LOG(ERROR) << "Error to write block set to memory";
return -1;
}
#endif
iter++;
continue;
}
ret = lseek64(fd, offset, SEEK_SET);
if (ret == -1) {
LOG(ERROR) << "BlockSet::WriteZeroToBlock Fail to seek";
return -1;
}
for (size_t pos = iter->first; pos < iter->second; pos++) {
if (Utils::WriteFully(fd, buffer.data(), H_BLOCK_SIZE)) {
continue;
}
if (errno == EIO) {
return 1;
}
LOG(ERROR) << "BlockSet::WriteZeroToBlock Write 0 to block error";
return -1;
}
iter++;
}
return 0;
}
int32_t BlockSet::WriteDiffToBlock(const Command &cmd, std::vector<uint8_t> &sourceBuffer, uint8_t *patchBuffer,
size_t patchLength, bool isImgDiff)
{
size_t srcBuffSize = sourceBuffer.size();
if (isImgDiff) {
std::vector<uint8_t> empty;
UpdatePatch::PatchParam patchParam = {sourceBuffer.data(), srcBuffSize, patchBuffer, patchLength};
auto writer = std::make_unique<BlockWriter>(cmd.GetTargetFileDescriptor(), offset_, *this);
if (writer.get() == nullptr) {
LOG(ERROR) << "Cannot create block writer, pkgdiff patch abort!";
return -1;
}
int32_t ret = UpdatePatch::UpdateApplyPatch::ApplyImagePatch(patchParam, empty,
[&](size_t start, const UpdatePatch::BlockBuffer &data, size_t size) -> int {
return (writer->Write(data.buffer, size, nullptr)) ? 0 : -1;
}, cmd.GetArgumentByPos(H_DIFF_CMD_ARGS_START + 1));
writer.reset();
if (ret != 0) {
LOG(ERROR) << "Fail to ApplyImagePatch";
return -1;
}
} else {
LOG(DEBUG) << "Run bsdiff patch.";
UpdatePatch::PatchBuffer patchInfo = {patchBuffer, 0, patchLength};
auto writer = std::make_unique<BlockWriter>(cmd.GetTargetFileDescriptor(), offset_, *this);
if (writer.get() == nullptr) {
LOG(ERROR) << "Cannot create block writer, pkgdiff patch abort!";
return -1;
}
auto ret = UpdatePatch::UpdateApplyPatch::ApplyBlockPatch(patchInfo, {sourceBuffer.data(), srcBuffSize},
[&](size_t start, const UpdatePatch::BlockBuffer &data, size_t size) -> int {
return (writer->Write(data.buffer, size, nullptr)) ? 0 : -1;
}, cmd.GetArgumentByPos(H_DIFF_CMD_ARGS_START + 1));
writer.reset();
if (ret != 0) {
LOG(ERROR) << "Fail to ApplyBlockPatch";
return -1;
}
}
if (fsync(cmd.GetTargetFileDescriptor()) == -1) {
LOG(ERROR) << "Failed to sync restored data";
return -1;
}
return 0;
}
bool BlockSet::CompareDataFromBlock(int fd, const std::vector<uint8_t> &buffer) const
{
constexpr size_t blockSize = static_cast<size_t>(H_BLOCK_SIZE);
const size_t totalBytes = TotalBlockSize() * blockSize;
if (totalBytes != buffer.size()) {
LOG(ERROR) << "Buffer size mismatch. block bytes: " << totalBytes << " != buffer size: " << buffer.size();
return false;
}
constexpr size_t maxBufferSize = 128 * 1024;
const size_t bufferSize = std::min(totalBytes, maxBufferSize);
std::vector<uint8_t> tempBuffer(bufferSize);
size_t bufferPos = 0;
for (size_t i = 0; i < blocks_.size(); ++i) {
const BlockPair &block = blocks_[i];
const size_t blockBytes = (block.second - block.first) * blockSize;
if (lseek64(fd, static_cast<off64_t>(block.first * blockSize + offset_), SEEK_SET) == -1) {
LOG(ERROR) << "Seek error. index: " << i << ", block(" << block.first << "," << block.second << ")";
return false;
}
size_t remaining = blockBytes;
while (remaining > 0) {
const size_t readSize = std::min(remaining, bufferSize);
if (!Utils::ReadFully(fd, tempBuffer.data(), readSize)) {
LOG(ERROR) << "Read error. index: " << i <<
", block(" << block.first << ", " << block.second << "), remaining: " << remaining;
return false;
}
if (!CompareBlocks(tempBuffer.data(), buffer.data() + bufferPos, readSize)) {
LOG(ERROR) << "Data mismatch. index: " << i <<
", block(" << block.first << ", " << block.second <<
"), remaining: " << remaining << ", bufferPos: " << bufferPos;
return false;
}
bufferPos += readSize;
remaining -= readSize;
}
}
return true;
}
bool BlockSet::CompareBlocks(const uint8_t *lhs, const uint8_t *rhs, size_t size) const
{
constexpr size_t blockSize = static_cast<size_t>(H_BLOCK_SIZE);
for (size_t offset = 0; offset < size; offset += blockSize) {
const size_t currSize = std::min(size - offset, blockSize);
if (memcmp(lhs + offset, rhs + offset, currSize) != 0) {
LOG(ERROR) << "memcmp failed. block offset: " << offset << ", currSize: " << currSize;
Utils::PrintHex(lhs + offset, currSize);
Utils::PrintHex(rhs + offset, currSize);
return false;
}
}
return true;
}
}