blob: 9894e8bdb5b0c484eddd1855f15af8c98d0b37af [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/storage/fvm/sparse_reader.h"
#include <fcntl.h>
#include <zircon/assert.h>
#include <algorithm>
#include <cinttypes>
#include <memory>
#include <utility>
#include <vector>
namespace fvm {
namespace {
class FileReader : public ReaderInterface {
public:
FileReader(fbl::unique_fd fd) : fd_(std::move(fd)) {}
virtual ~FileReader() = default;
virtual zx_status_t Read(void* buf, size_t buf_size, size_t* size_actual) final {
auto n = read(fd_.get(), buf, buf_size);
if (n < 0) {
return ZX_ERR_IO;
}
*size_actual = static_cast<size_t>(n);
return ZX_OK;
}
private:
fbl::unique_fd fd_;
};
} // namespace
using Buffer = internal::Buffer;
Buffer::Buffer(uint64_t offset, size_t size, uint64_t capacity) : capacity_(capacity) {
info_.size = size;
info_.offset = offset;
data_.reset(new uint8_t[capacity]);
}
bool Buffer::IsEmpty() const { return info_.offset == 0 && info_.size == 0; }
void Buffer::Write(uint8_t* data, size_t length) {
ZX_ASSERT(length <= capacity_);
// We should have read all previous data from buffer before writing more.
ZX_ASSERT(IsEmpty());
if (length > 0) {
memcpy(data_.get(), data, length);
info_.size = length;
}
}
void Buffer::Read(uint8_t* target, size_t length, size_t* actual) {
size_t cp_sz = std::min(length, info_.size);
if (cp_sz > 0) {
memcpy(target, data_.get() + info_.offset, cp_sz);
info_.offset += cp_sz;
}
info_.size -= cp_sz;
if (info_.size == 0) {
info_.offset = 0;
}
*actual = cp_sz;
}
zx_status_t SparseReader::Create(fbl::unique_fd fd, std::unique_ptr<SparseReader>* out) {
return SparseReader::CreateHelper(std::make_unique<FileReader>(std::move(fd)), true /* verbose */,
out);
}
zx_status_t SparseReader::Create(std::unique_ptr<ReaderInterface> reader,
std::unique_ptr<SparseReader>* out) {
return SparseReader::CreateHelper(std::move(reader), true /* verbose */, out);
}
zx_status_t SparseReader::CreateSilent(fbl::unique_fd fd, std::unique_ptr<SparseReader>* out) {
return SparseReader::CreateHelper(std::make_unique<FileReader>(std::move(fd)),
false /* verbose */, out);
}
zx_status_t SparseReader::CreateHelper(std::unique_ptr<ReaderInterface> reader_intf, bool verbose,
std::unique_ptr<SparseReader>* out) {
std::unique_ptr<SparseReader> reader(new SparseReader(std::move(reader_intf), verbose));
zx_status_t status;
if ((status = reader->ReadMetadata()) != ZX_OK) {
return status;
}
*out = std::move(reader);
return ZX_OK;
}
SparseReader::SparseReader(std::unique_ptr<ReaderInterface> reader, bool verbose)
: compressed_(false), verbose_(verbose), reader_(std::move(reader)) {}
zx_status_t SparseReader::ReadMetadata() {
// Read sparse image header.
fvm::SparseImage image;
size_t actual;
auto status = ReadRaw(reinterpret_cast<uint8_t*>(&image), sizeof(fvm::SparseImage), &actual);
if (status != ZX_OK || actual != sizeof(fvm::SparseImage)) {
fprintf(stderr, "failed to read the sparse header\n");
return ZX_ERR_IO;
}
// Verify the header.
if (image.magic != fvm::kSparseFormatMagic) {
if (verbose_) {
fprintf(stderr, "SparseReader: Bad magic (got %" PRIx64 ")\n", image.magic);
}
return ZX_ERR_BAD_STATE;
} else if (image.version != fvm::kSparseFormatVersion) {
fprintf(stderr, "SparseReader: Unexpected sparse file version\n");
return ZX_ERR_BAD_STATE;
}
ZX_ASSERT(image.header_length > sizeof(image));
metadata_.reset(new uint8_t[image.header_length]);
memcpy(metadata_.get(), &image, sizeof(image));
// Read remainder of metadata.
size_t off = sizeof(image);
while (off < image.header_length) {
status = ReadRaw(&metadata_[off], image.header_length - off, &actual);
if (status != ZX_OK) {
fprintf(stderr, "SparseReader: Failed to read metadata\n");
return status;
}
off += actual;
}
// If image is compressed, additional setup is required
if (image.flags & fvm::kSparseFlagLz4) {
if (verbose_) {
printf("Found compressed file\n");
}
compressed_ = true;
if (auto status = SetupLZ4(); status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
zx_status_t SparseReader::SetupLZ4() {
zx_status_t status;
size_t actual;
// Initialize decompression context
LZ4F_errorCode_t errc = LZ4F_createDecompressionContext(&dctx_, LZ4F_VERSION);
if (LZ4F_isError(errc)) {
fprintf(stderr, "SparseReader: could not initialize decompression: %s\n",
LZ4F_getErrorName(errc));
return ZX_ERR_INTERNAL;
}
size_t src_sz = 4;
size_t dst_sz = 0;
std::unique_ptr<uint8_t[]> inbufptr(new uint8_t[src_sz]);
uint8_t* inbuf = inbufptr.get();
// Read first 4 bytes to let LZ4 tell us how much it expects in the first pass.
status = ReadRaw(inbuf, src_sz, &actual);
if (status != ZX_OK || actual < src_sz) {
fprintf(stderr, "SparseReader: could not read from input\n");
return ZX_ERR_IO;
}
// Run decompress once to find out how much data we should read for the next decompress run
// Since we are not yet decompressing any actual data, the dst_buffer is null
to_read_ = LZ4F_decompress(dctx_, nullptr, &dst_sz, inbuf, &src_sz, nullptr);
if (LZ4F_isError(to_read_)) {
fprintf(stderr, "SparseReader: could not decompress header: %s\n", LZ4F_getErrorName(to_read_));
return ZX_ERR_INTERNAL;
}
if (to_read_ > LZ4_MAX_BLOCK_SIZE) {
to_read_ = LZ4_MAX_BLOCK_SIZE;
}
// Initialize data buffers
if ((status = InitializeBuffer(LZ4_MAX_BLOCK_SIZE, &out_)) != ZX_OK) {
return status;
} else if ((status = InitializeBuffer(LZ4_MAX_BLOCK_SIZE, &in_)) != ZX_OK) {
return status;
}
return ZX_OK;
}
zx_status_t SparseReader::InitializeBuffer(size_t size, Buffer* out_buffer) {
if (size < LZ4_MAX_BLOCK_SIZE) {
fprintf(stderr, "Buffer size must be >= %d\n", LZ4_MAX_BLOCK_SIZE);
return ZX_ERR_INVALID_ARGS;
}
*out_buffer = Buffer(0, 0, size);
return ZX_OK;
}
SparseReader::~SparseReader() {
PrintStats();
if (compressed_) {
LZ4F_freeDecompressionContext(dctx_);
}
}
fvm::SparseImage* SparseReader::Image() {
return reinterpret_cast<fvm::SparseImage*>(metadata_.get());
}
fvm::PartitionDescriptor* SparseReader::Partitions() {
return reinterpret_cast<fvm::PartitionDescriptor*>(reinterpret_cast<uintptr_t>(metadata_.get()) +
sizeof(fvm::SparseImage));
}
zx_status_t SparseReader::ReadData(uint8_t* data, size_t length, size_t* actual) {
#ifdef __Fuchsia__
zx_ticks_t start = zx_ticks_get();
#endif
size_t total_size = 0;
if (compressed_) {
if (out_.IsEmpty() && to_read_ == 0) {
fprintf(stderr, "Attempting to read past end of the compressed extent. At offset: %lu\n",
raw_bytes_read_);
fprintf(stderr, "Last raw read of size %lu ended with:", in_.size());
for (size_t i = in_.size() - std::min(16ul, in_.size()); i < in_.size(); ++i) {
fprintf(stderr, " %#02x", in_.get()[i]);
}
fprintf(stderr, "\n");
// There is no more to read
return ZX_ERR_OUT_OF_RANGE;
}
// Read previously decompressed data from buffer if possible
out_.Read(data, length, &total_size);
// If we still have data to read, start decompression (reading more from fd as needed)
while (total_size < length && to_read_ > 0) {
// Make sure data to read does not exceed max, and both buffers are empty
ZX_ASSERT(out_.IsEmpty());
ZX_ASSERT(in_.IsEmpty());
ZX_ASSERT(to_read_ <= in_.capacity());
// Read specified amount from fd
zx_status_t status;
if ((status = ReadRaw(in_.get(), to_read_, &(in_.info()->size))) != ZX_OK) {
return status;
}
size_t src_sz = in_.size();
size_t next = 0;
// Decompress all compressed data
while (in_.offset() < to_read_) {
size_t dst_sz = out_.capacity() - out_.size();
next = LZ4F_decompress(dctx_, out_.get() + out_.size(), &dst_sz, in_.get() + in_.offset(),
&src_sz, nullptr);
if (LZ4F_isError(next)) {
fprintf(stderr, "could not decompress input: %s\n", LZ4F_getErrorName(next));
return -1;
}
out_.info()->size += dst_sz;
in_.info()->offset += src_sz;
in_.info()->size -= src_sz;
src_sz = to_read_ - in_.offset();
}
// Make sure we have read all data from in_buf_
if (in_.size() > 0) {
return ZX_ERR_IO;
}
in_.info()->offset = 0;
// Copy newly decompressed data from outbuf
size_t cp = std::min(length - total_size, static_cast<size_t>(out_.size()));
out_.Read(data + total_size, cp, &cp);
total_size += cp;
to_read_ = next;
if (to_read_ > LZ4_MAX_BLOCK_SIZE) {
to_read_ = LZ4_MAX_BLOCK_SIZE;
}
}
} else {
zx_status_t status = ReadRaw(data, length, &total_size);
if (status != ZX_OK) {
return status;
}
}
#ifdef __Fuchsia__
total_time_ += zx_ticks_get() - start;
#endif
*actual = total_size;
return ZX_OK;
}
zx_status_t SparseReader::ReadRaw(uint8_t* data, size_t length, size_t* actual) {
#ifdef __Fuchsia__
zx_ticks_t start = zx_ticks_get();
#endif
zx_status_t status;
size_t size_actual;
size_t total_size = 0;
size_t bytes_left = length;
while ((status = reader_->Read(data + total_size, bytes_left, &size_actual)) == ZX_OK &&
size_actual > 0) {
total_size += size_actual;
bytes_left -= size_actual;
if (bytes_left == 0) {
break;
}
}
#ifdef __Fuchsia__
read_time_ += zx_ticks_get() - start;
#endif
if (status != ZX_OK) {
return status;
}
raw_bytes_read_ += total_size;
*actual = total_size;
return ZX_OK;
}
zx_status_t SparseReader::WriteDecompressed(fbl::unique_fd outfd) {
if (!compressed_) {
fprintf(stderr, "BlockReader: File is not compressed\n");
return ZX_ERR_INVALID_ARGS;
}
// Update metadata and write to new file.
fvm::SparseImage* image = Image();
image->flags &= ~fvm::kSparseFlagLz4;
if (write(outfd.get(), metadata_.get(), image->header_length) !=
static_cast<ssize_t>(image->header_length)) {
fprintf(stderr, "BlockReader: could not write header to out file\n");
return -1;
}
// Read/write decompressed data in LZ4_MAX_BLOCK_SIZE chunks.
while (true) {
zx_status_t status;
uint8_t data[LZ4_MAX_BLOCK_SIZE];
size_t length;
if ((status = ReadData(data, LZ4_MAX_BLOCK_SIZE, &length)) != ZX_OK) {
if (status == ZX_ERR_OUT_OF_RANGE) {
return ZX_OK;
}
return status;
}
if (write(outfd.get(), data, length) != static_cast<ssize_t>(length)) {
fprintf(stderr, "BlockReader: failed to write to output\n");
return ZX_ERR_IO;
}
}
}
void SparseReader::PrintStats() const {
if (verbose_) {
printf("Reading FVM from compressed file: %s\n", compressed_ ? "true" : "false");
printf("Remaining bytes read into compression buffer: \%" PRIuMAX "\n", in_.size());
printf("Remaining bytes written to decompression buffer:\%" PRIuMAX "\n", out_.size());
#ifdef __Fuchsia__
printf("Time reading bytes from sparse FVM file: %lu (%lu s)\n", read_time_,
read_time_ / zx_ticks_per_second());
printf("Time reading bytes AND decompressing them: %lu (%lu s)\n", total_time_,
total_time_ / zx_ticks_per_second());
#endif
}
}
zx_status_t SparseReader::DecompressLZ4File(const char* in_file, const char* out_file) {
fbl::unique_fd fd_in(open(in_file, O_RDONLY, 0644));
if (!fd_in) {
fprintf(stderr, "Unable to open input lz4 file %s\n", in_file);
return ZX_ERR_IO;
}
fbl::unique_fd fd_out(open(out_file, O_RDWR | O_CREAT, 0644));
if (!fd_out) {
fprintf(stderr, "Unable to create output file %s\n", out_file);
return ZX_ERR_IO;
}
std::unique_ptr<SparseReader> reader(
new SparseReader(std::make_unique<FileReader>(std::move(fd_in)), false));
reader->compressed_ = true;
reader->SetupLZ4();
// Read/write decompressed data in LZ4_MAX_BLOCK_SIZE chunks.
std::vector<uint8_t> buffer(LZ4_MAX_BLOCK_SIZE);
while (true) {
zx_status_t status;
size_t length;
if ((status = reader->ReadData(buffer.data(), LZ4_MAX_BLOCK_SIZE, &length)) != ZX_OK) {
if (status == ZX_ERR_OUT_OF_RANGE) {
return ZX_OK;
}
return status;
}
if (write(fd_out.get(), buffer.data(), length) != static_cast<ssize_t>(length)) {
fprintf(stderr, "BlockReader: failed to write to output\n");
return ZX_ERR_IO;
}
}
return ZX_OK;
}
} // namespace fvm