| // 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 <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() = default; |
| 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]); |
| } |
| Buffer::Buffer(Buffer&&) = default; |
| Buffer& Buffer::operator=(Buffer&&) = default; |
| Buffer::~Buffer() = default; |
| |
| 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 = reader_->Read(&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 = reader_->Read(&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 = reader_->Read(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, NULL); |
| 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) { |
| // 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, NULL); |
| 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; |
| } |
| |
| *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 |