blob: d5ce0f595ce62a2a0a673b229a7b8fe23caa48df [file] [log] [blame]
// Copyright 2020 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 "zstd-seekable-blob.h"
#include <lib/fzl/vmo-mapper.h>
#include <lib/sync/completion.h>
#include <stdint.h>
#include <string.h>
#include <zircon/compiler.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <memory>
#include <vector>
#include <fbl/algorithm.h>
#include <fbl/auto_call.h>
#include <fs/trace.h>
#include <zstd/zstd_seekable.h>
#include "zstd-seekable-blob-collection.h"
#include "zstd-seekable.h"
namespace blobfs {
namespace {
int ComputeOffsetAndNumBytesForRead(ZSTDSeekableFile* file, size_t num_bytes,
uint32_t* out_data_block_offset, uint32_t* out_num_blocks,
uint64_t* out_data_byte_offset) {
// |file->byte_offset| does not account for ZSTD seekable header.
uint64_t data_byte_offset;
if (add_overflow(kZSTDSeekableHeaderSize, file->byte_offset, &data_byte_offset)) {
FS_TRACE_ERROR("[blobfs][zstd-seekable] ZSTD header + file offset overflow: file_offset=%llu\n",
file->byte_offset);
file->status = ZX_ERR_OUT_OF_RANGE;
return -1;
}
// Safely convert units: Bytes to blocks.
uint64_t data_block_start64 = data_byte_offset / kBlobfsBlockSize;
if (data_block_start64 > std::numeric_limits<uint32_t>::max()) {
FS_TRACE_ERROR("[blobfs][zstd-seekable] Oversized data block start: %lu / %u = %lu > %u\n",
data_byte_offset, kBlobfsBlockSize, data_block_start64,
std::numeric_limits<uint32_t>::max());
file->status = ZX_ERR_OUT_OF_RANGE;
return -1;
}
uint32_t data_block_start = static_cast<uint32_t>(data_block_start64);
// Compute raw offset to end before determining which blocks must be read.
uint64_t data_byte_end;
if (add_overflow(data_byte_offset, num_bytes, &data_byte_end)) {
FS_TRACE_ERROR(
"[blobfs][zstd-seekable] Oversized data block end: data_byte_offset=%lu, num_bytes=%lu\n",
data_byte_offset, num_bytes);
file->status = ZX_ERR_OUT_OF_RANGE;
return -1;
}
// Round up to nearest block from end, then subtract start to determine number of blocks.
uint64_t data_block_end64 = fbl::round_up(data_byte_end, kBlobfsBlockSize) / kBlobfsBlockSize;
uint64_t num_blocks64;
if (sub_overflow(data_block_end64, data_block_start64, &num_blocks64)) {
FS_TRACE_ERROR(
"[blobfs][zstd-seekable] Block calculation error: (data_block_end=%lu - "
"data_block_start=%lu) should be non-negative\n",
data_block_end64, data_block_start64);
file->status = ZX_ERR_INTERNAL;
return -1;
}
if (num_blocks64 > std::numeric_limits<uint32_t>::max()) {
FS_TRACE_ERROR("[blobfs][zstd-seekable] Oversized number of blocks: %lu > %u\n", num_blocks64,
std::numeric_limits<uint32_t>::max());
file->status = ZX_ERR_OUT_OF_RANGE;
return -1;
}
uint32_t num_blocks = static_cast<uint32_t>(num_blocks64);
*out_data_block_offset = data_block_start;
*out_num_blocks = num_blocks;
*out_data_byte_offset = data_byte_offset;
return 0;
}
} // namespace
// ZSTD Seekable Format API function for `ZSTD_seekable_customFile`.
int ZSTDRead(void* void_ptr_zstd_seekable_file, void* buf, size_t num_bytes) {
ZX_DEBUG_ASSERT(void_ptr_zstd_seekable_file);
auto* file = static_cast<ZSTDSeekableFile*>(void_ptr_zstd_seekable_file);
// Give up if any file operation has ever failed.
if (file->status != ZX_OK) {
return -1;
}
TRACE_DURATION("blobfs", "ZSTDRead", "byte_offset", file->byte_offset, "bytes", num_bytes);
if (num_bytes == 0) {
return 0;
}
uint32_t data_block_offset;
uint32_t num_blocks;
uint64_t data_byte_offset;
int result = ComputeOffsetAndNumBytesForRead(file, num_bytes, &data_block_offset, &num_blocks,
&data_byte_offset);
if (result != 0) {
// Note: |zx_status_t|, tracing/logging managed by |ComputeOffsetAndNumBytesForRead|.
return result;
}
// Delegate block-level read to compressed block collection.
zx_status_t status = file->blocks->Read(data_block_offset, num_blocks);
if (status != ZX_OK) {
FS_TRACE_ERROR("[blobfs][zstd-seekable] Failed to read blocks: %s\n",
zx_status_get_string(status));
file->status = status;
return -1;
}
// Copy from transfer buffer to |buf|.
{
TRACE_DURATION("blobfs", "ZSTDRead::Copy", "byte_offset", file->byte_offset, "bytes",
num_bytes);
uint32_t start = static_cast<uint32_t>(data_byte_offset % kBlobfsBlockSize);
static_assert(sizeof(const void*) == sizeof(uint64_t));
uint64_t start_ptr;
uint64_t end_ptr;
if (add_overflow(reinterpret_cast<uint64_t>(file->blob->compressed_data_start()), start,
&start_ptr) ||
add_overflow(start_ptr, num_bytes, &end_ptr)) {
FS_TRACE_ERROR("[blobfs][zstd-seekable] VMO offset overflow: offset=%u length=%zu\n", start,
num_bytes);
file->status = ZX_ERR_OUT_OF_RANGE;
return -1;
}
memcpy(buf, file->blob->compressed_data_start() + start, num_bytes);
}
// Advance byte offset in file.
unsigned long long new_byte_offset;
if (add_overflow(file->byte_offset, num_bytes, &new_byte_offset)) {
FS_TRACE_ERROR("[blobfs][zstd-seekable] Byte offset overflow: file_offset=%llu increment=%lu\n",
file->byte_offset, num_bytes);
file->status = ZX_ERR_OUT_OF_RANGE;
return -1;
}
file->byte_offset = new_byte_offset;
return 0;
}
// ZSTD Seekable Format API function for `ZSTD_seekable_customFile`.
int ZSTDSeek(void* void_ptr_zstd_seekable_file, long long byte_offset, int origin) {
ZX_DEBUG_ASSERT(void_ptr_zstd_seekable_file);
auto* file = static_cast<ZSTDSeekableFile*>(void_ptr_zstd_seekable_file);
// Give up if any file operation has ever failed.
if (file->status != ZX_OK) {
return -1;
}
unsigned long long new_byte_offset = file->byte_offset;
switch (origin) {
// Absolute offset:
// Set position in ZSTD archive to |byte_offset|.
case SEEK_SET:
if (byte_offset < 0) {
FS_TRACE_ERROR("[blobfs][zstd-seekable] Seek absolute underflow: offset=%lld\n",
byte_offset);
file->status = ZX_ERR_OUT_OF_RANGE;
return -1;
}
new_byte_offset = byte_offset;
break;
// Relative-to-current offset:
// Set position in ZSTD archive to |file->byte_offset + byte_offset|.
case SEEK_CUR:
if (byte_offset < 0 && file->byte_offset + byte_offset >= file->byte_offset) {
FS_TRACE_ERROR(
"[blobfs][zstd-seekable] Seek from current position underflow: current=%llu "
"offset=%lld\n",
file->byte_offset, byte_offset);
file->status = ZX_ERR_OUT_OF_RANGE;
return -1;
}
new_byte_offset = file->byte_offset + byte_offset;
break;
// Relative-to-end offset:
// Set position in ZSTD archive to |file->num_bytes + byte_offset|.
case SEEK_END:
if (byte_offset < 0 && file->num_bytes + byte_offset >= file->num_bytes) {
FS_TRACE_ERROR("[blobfs][zstd-seekable] Seek from end underflow: end=%llu offset=%lld\n",
file->num_bytes, byte_offset);
file->status = ZX_ERR_OUT_OF_RANGE;
return -1;
}
new_byte_offset = file->num_bytes + byte_offset;
break;
default:
FS_TRACE_ERROR("[blobfs][zstd-seekable] Invalid seek origin enum value: %d\n", origin);
return -1;
}
// New offset must not go passed end of file.
if (new_byte_offset > file->num_bytes) {
FS_TRACE_ERROR("[blobfs][zstd-seekable] Seek passed end of file: end=%llu offset=%lld\n",
file->num_bytes, new_byte_offset);
file->status = ZX_ERR_OUT_OF_RANGE;
return -1;
}
file->byte_offset = new_byte_offset;
return 0;
}
zx_status_t ZSTDSeekableBlob::Create(
uint32_t node_index, fzl::VmoMapper* mapped_vmo,
std::unique_ptr<ZSTDCompressedBlockCollection> compressed_block_collection,
std::unique_ptr<ZSTDSeekableBlob>* out) {
std::unique_ptr<ZSTDSeekableBlob> blob(
new ZSTDSeekableBlob(node_index, mapped_vmo, std::move(compressed_block_collection)));
zx_status_t status = blob->ReadHeader();
if (status != ZX_OK) {
return status;
}
*out = std::move(blob);
return ZX_OK;
}
zx_status_t ZSTDSeekableBlob::Read(uint8_t* buf, uint64_t data_byte_offset, uint64_t num_bytes) {
TRACE_DURATION("blobfs", "ZSTDSeekableBlob::Read", "data byte offset", data_byte_offset,
"num bytes", num_bytes);
ZSTD_seekable* d_stream = ZSTD_seekable_create();
if (d_stream == nullptr) {
FS_TRACE_ERROR("[blobfs][zstd-seekable] Failed to create seekable dstream\n");
return ZX_ERR_INTERNAL;
}
auto clean_up = fbl::MakeAutoCall([&]() { ZSTD_seekable_free(d_stream); });
ZSTDSeekableFile zstd_seekable_file{
.blob = this,
.blocks = compressed_block_collection_.get(),
.byte_offset = 0,
.num_bytes = header_.archive_size,
.status = ZX_OK,
};
size_t zstd_return = ZSTD_seekable_initAdvanced(d_stream, ZSTD_seekable_customFile{
.opaque = &zstd_seekable_file,
.read = ZSTDRead,
.seek = ZSTDSeek,
});
if (ZSTD_isError(zstd_return)) {
FS_TRACE_ERROR("[blobfs][zstd-seekable] Failed to initialize seekable dstream: %s\n",
ZSTD_getErrorName(zstd_return));
return ZX_ERR_INTERNAL;
}
size_t decompressed = 0;
do {
zstd_return =
ZSTD_seekable_decompress(d_stream, buf, num_bytes, data_byte_offset + decompressed);
if (ZSTD_isError(zstd_return)) {
FS_TRACE_ERROR("[blobfs][zstd-seekable] Failed to decompress: %s\n",
ZSTD_getErrorName(zstd_return));
if (zstd_seekable_file.status != ZX_OK) {
return zstd_seekable_file.status;
}
return ZX_ERR_IO_DATA_INTEGRITY;
}
// Non-error case: |ZSTD_seekable_decompress| returns number of bytes decompressed.
decompressed += zstd_return;
// From the ZSTD_seekable_decompress Documentation:
// The return value is the number of bytes decompressed, or an error code checkable with
// ZSTD_isError().
// Assume that a return value of 0 indicates, not only that 0 bytes were decompressed, but also
// that there are no more bytes to decompress.
} while (zstd_return > 0 && decompressed < num_bytes);
// TODO(markdittmer): Perform verification over block-aligned data that was read.
return ZX_OK;
}
ZSTDSeekableBlob::ZSTDSeekableBlob(
uint32_t node_index, fzl::VmoMapper* mapped_vmo,
std::unique_ptr<ZSTDCompressedBlockCollection> compressed_block_collection)
: node_index_(node_index),
mapped_vmo_(mapped_vmo),
compressed_block_collection_(std::move(compressed_block_collection)) {}
zx_status_t ZSTDSeekableBlob::ReadHeader() {
// The header is an internal BlobFS data structure that fits into one block.
static_assert(kZSTDSeekableHeaderSize <= kBlobfsBlockSize);
const uint32_t read_num_blocks = 1;
const size_t read_num_bytes = kBlobfsBlockSize;
zx_status_t status = compressed_block_collection_->Read(0, read_num_blocks);
if (status != ZX_OK) {
FS_TRACE_ERROR("[blobfs][zstd-seekable] Failed to read header block: %s\n",
zx_status_get_string(status));
return status;
}
return ZSTDSeekableDecompressor::ReadHeader(mapped_vmo_->start(), read_num_bytes, &header_);
}
} // namespace blobfs