blob: 9231c8cd0e381d7d982fdaf716a9ac249aae8021 [file] [log] [blame]
// Copyright 2021 The Cobalt Authors. All Rights Reserved.
//
// 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 <vector>
#include "starboard/elf_loader/lz4_file_impl.h"
#include "starboard/common/log.h"
#include "starboard/memory.h"
namespace starboard {
namespace elf_loader {
namespace {
// The size of the buffer that the file to be decompressed is read into, 16KiB.
// This value was explicitly chosen after testing various buffer sizes on a
// Raspberry Pi 2.
constexpr int kCompressedBufferSize = 16 * 1024;
// The rate of growth of the buffer used for decompression, 32KiB.
constexpr int kDecompressedGrowthSize = 32 * 1024;
} // namespace
LZ4FileImpl::LZ4FileImpl() {
const LZ4F_errorCode_t lz4f_error_code =
LZ4F_createDecompressionContext(&lz4f_context_, LZ4F_VERSION);
if (lz4f_error_code != 0) {
SB_LOG(ERROR) << LZ4F_getErrorName(lz4f_error_code);
lz4f_context_ = nullptr;
}
}
LZ4FileImpl::~LZ4FileImpl() {
if (!lz4f_context_) {
return;
}
const LZ4F_errorCode_t lz4f_error_code =
LZ4F_freeDecompressionContext(lz4f_context_);
if (lz4f_error_code != 0) {
SB_LOG(ERROR) << LZ4F_getErrorName(lz4f_error_code);
}
}
bool LZ4FileImpl::Open(const char* name) {
SB_DCHECK(name);
if (!lz4f_context_) {
return false;
}
SbFileInfo file_info;
if (!FileImpl::Open(name) || !SbFileGetInfo(file_, &file_info)) {
return false;
}
std::vector<char> compressed_data(kCompressedBufferSize);
char* compressed_buffer = compressed_data.data();
char* decompressed_buffer = nullptr;
size_t compressed_size_remaining = file_info.size;
size_t decompressed_size_current = 0;
// Assume the content size will double when decompressed.
decompressed_data_.resize(file_info.size * 2);
while (true) {
if (!FileImpl::ReadFromOffset(file_info.size - compressed_size_remaining,
compressed_data.data(),
kCompressedBufferSize)) {
decompressed_data_.resize(0);
return false;
}
size_t compressed_buffer_offset = 0;
compressed_buffer = compressed_data.data();
while (compressed_buffer_offset < kCompressedBufferSize) {
// Ensure we always have a minimum of |kDecompressedGrowthSize| bytes
// available.
if (decompressed_data_.size() - decompressed_size_current <
kDecompressedGrowthSize) {
decompressed_data_.resize(decompressed_size_current +
kDecompressedGrowthSize);
}
// We need to set |decompressed_buffer| each iteration since resizing
// |decompressed_data_| invalidates the pointer.
decompressed_buffer =
decompressed_data_.data() + decompressed_size_current;
size_t compressed =
compressed_size_remaining < kCompressedBufferSize
? compressed_size_remaining
: kCompressedBufferSize - compressed_buffer_offset;
size_t decompressed =
decompressed_data_.size() - decompressed_size_current;
const size_t result =
LZ4F_decompress(lz4f_context_, decompressed_buffer, &decompressed,
compressed_buffer, &compressed, nullptr);
if (LZ4F_isError(result)) {
SB_LOG(ERROR) << LZ4F_getErrorName(result);
LZ4F_resetDecompressionContext(lz4f_context_);
decompressed_data_.resize(0);
return false;
}
compressed_size_remaining -= compressed;
decompressed_size_current += decompressed;
if (compressed_size_remaining <= 0) {
decompressed_data_.resize(decompressed_size_current);
return true;
}
compressed_buffer_offset += compressed;
compressed_buffer += compressed;
}
}
return false;
}
bool LZ4FileImpl::ReadFromOffset(int64_t offset, char* buffer, int size) {
SB_DCHECK(lz4f_context_);
if ((offset < 0) || (offset + size >= decompressed_data_.size())) {
return false;
}
SbMemoryCopy(buffer, decompressed_data_.data() + offset, size);
return true;
}
} // namespace elf_loader
} // namespace starboard