blob: 4c9d756e83c0d856844d62316aaff35a0083be25 [file] [log] [blame] [edit]
// 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 <inttypes.h>
#include "fvm/container.h"
static LZ4F_preferences_t lz4_prefs = {
.frameInfo = {
.blockSizeID = LZ4F_max64KB,
.blockMode = LZ4F_blockIndependent,
},
.compressionLevel = 0,
};
zx_status_t CompressionContext::Setup(size_t max_len) {
LZ4F_errorCode_t errc = LZ4F_createCompressionContext(&cctx_, LZ4F_VERSION);
if (LZ4F_isError(errc)) {
fprintf(stderr, "Could not create compression context: %s\n", LZ4F_getErrorName(errc));
return ZX_ERR_INTERNAL;
}
Reset(LZ4F_compressBound(max_len, &lz4_prefs));
size_t r = LZ4F_compressBegin(cctx_, GetBuffer(), GetRemaining(), &lz4_prefs);
if (LZ4F_isError(r)) {
fprintf(stderr, "Could not begin compression: %s\n", LZ4F_getErrorName(r));
return ZX_ERR_INTERNAL;
}
IncreaseOffset(r);
return ZX_OK;
}
zx_status_t CompressionContext::Compress(const void* data, size_t length) {
size_t r = LZ4F_compressUpdate(cctx_, GetBuffer(), GetRemaining(), data, length, NULL);
if (LZ4F_isError(r)) {
fprintf(stderr, "Could not compress data: %s\n", LZ4F_getErrorName(r));
return ZX_ERR_INTERNAL;
}
IncreaseOffset(r);
return ZX_OK;
}
zx_status_t CompressionContext::Finish() {
size_t r = LZ4F_compressEnd(cctx_, GetBuffer(), GetRemaining(), NULL);
if (LZ4F_isError(r)) {
fprintf(stderr, "Could not finish compression: %s\n", LZ4F_getErrorName(r));
return ZX_ERR_INTERNAL;
}
IncreaseOffset(r);
LZ4F_errorCode_t errc = LZ4F_freeCompressionContext(cctx_);
if (LZ4F_isError(errc)) {
fprintf(stderr, "Could not free compression context: %s\n", LZ4F_getErrorName(errc));
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
zx_status_t SparseContainer::Create(const char* path, size_t slice_size, uint32_t flags,
fbl::unique_ptr<SparseContainer>* out) {
fbl::AllocChecker ac;
fbl::unique_ptr<SparseContainer> sparseContainer(new (&ac) SparseContainer(path, slice_size,
flags));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
zx_status_t status;
if ((status = sparseContainer->Init()) != ZX_OK) {
return status;
}
*out = fbl::move(sparseContainer);
return ZX_OK;
}
SparseContainer::SparseContainer(const char* path, uint64_t slice_size, uint32_t flags)
: Container(path, slice_size, flags), valid_(false), disk_size_(0),
extent_size_(0) {
fd_.reset(open(path, O_CREAT | O_RDWR, 0666));
if (!fd_) {
fprintf(stderr, "Failed to open sparse data path\n");
return;
}
struct stat s;
if (fstat(fd_.get(), &s) < 0) {
fprintf(stderr, "Failed to stat %s\n", path);
return;
}
if (s.st_size > 0) {
disk_size_ = s.st_size;
if (read(fd_.get(), &image_, sizeof(fvm::sparse_image_t)) != sizeof(fvm::sparse_image_t)) {
fprintf(stderr, "SparseContainer: Failed to read the sparse header\n");
return;
}
if (image_.flags & fvm::kSparseFlagLz4) {
return;
}
extent_size_ = disk_size_ - image_.header_length;
for (unsigned i = 0; i < image_.partition_count; i++) {
partition_info_t partition;
partitions_.push_back(fbl::move(partition));
if (read(fd_.get(), &partitions_[i].descriptor, sizeof(fvm::partition_descriptor_t)) !=
sizeof(fvm::partition_descriptor_t)) {
fprintf(stderr, "SparseContainer: Failed to read partition %u\n", i);
return;
}
for (unsigned j = 0; j < partitions_[i].descriptor.extent_count; j++) {
fvm::extent_descriptor_t extent;
partitions_[i].extents.push_back(extent);
if (read(fd_.get(), &partitions_[i].extents[j], sizeof(fvm::extent_descriptor_t)) !=
sizeof(fvm::extent_descriptor_t)) {
fprintf(stderr, "SparseContainer: Failed to read extent\n");
return;
}
}
}
valid_ = true;
xprintf("Successfully read from existing sparse data container.\n");
}
}
SparseContainer::~SparseContainer() = default;
zx_status_t SparseContainer::Init() {
image_.magic = fvm::kSparseFormatMagic;
image_.version = fvm::kSparseFormatVersion;
image_.slice_size = slice_size_;
image_.partition_count = 0;
image_.header_length = sizeof(fvm::sparse_image_t);
image_.flags = flags_;
partitions_.reset();
dirty_ = true;
valid_ = true;
extent_size_ = 0;
xprintf("Initialized new sparse data container.\n");
return ZX_OK;
}
zx_status_t SparseContainer::Verify() const {
if (!valid_) {
fprintf(stderr, "SparseContainer: Found invalid container\n");
return ZX_ERR_INTERNAL;
}
if (image_.magic != fvm::kSparseFormatMagic) {
fprintf(stderr, "SparseContainer: Bad magic\n");
return ZX_ERR_IO;
}
xprintf("Slice size is %" PRIu64 "\n", image_.slice_size);
xprintf("Found %" PRIu64 " partitions\n", image_.partition_count);
off_t start = 0;
off_t end = image_.header_length;
for (unsigned i = 0; i < image_.partition_count; i++) {
fbl::Vector<size_t> extent_lengths;
start = end;
xprintf("Found partition %u with %u extents\n", i,
partitions_[i].descriptor.extent_count);
for (unsigned j = 0; j < partitions_[i].descriptor.extent_count; j++) {
extent_lengths.push_back(partitions_[i].extents[j].extent_length);
end += partitions_[i].extents[j].extent_length;
}
zx_status_t status;
disk_format_t part;
if ((status = Format::Detect(fd_.get(), start, &part)) != ZX_OK) {
return status;
}
fbl::unique_fd dupfd(dup(fd_.get()));
if (!dupfd) {
fprintf(stderr, "Failed to duplicate fd\n");
return ZX_ERR_INTERNAL;
}
if ((status = Format::Check(fbl::move(dupfd), start, end, extent_lengths, part)) != ZX_OK) {
const char* name = reinterpret_cast<const char*>(partitions_[i].descriptor.name);
fprintf(stderr, "%s fsck returned an error.\n", name);
return status;
}
}
if (end != disk_size_) {
fprintf(stderr, "Header + extent sizes (%" PRIu64 ") do not match sparse file size "
"(%zu)\n", end, disk_size_);
return ZX_ERR_IO_DATA_INTEGRITY;
}
return ZX_OK;
}
zx_status_t SparseContainer::Commit() {
if (!dirty_ || image_.partition_count == 0) {
fprintf(stderr, "Commit: Nothing to write.\n");
return ZX_OK;
}
// Reset file length to 0
if (ftruncate(fd_.get(), 0) != 0) {
fprintf(stderr, "Failed to truncate fvm container");
return ZX_ERR_IO;
}
// Recalculate and verify header length
uint64_t header_length = 0;
if (lseek(fd_.get(), 0, SEEK_SET) < 0) {
fprintf(stderr, "Seek reset failed\n");
return ZX_ERR_IO;
}
header_length += sizeof(fvm::sparse_image_t);
if (write(fd_.get(), &image_, sizeof(fvm::sparse_image_t)) != sizeof(fvm::sparse_image_t)) {
fprintf(stderr, "Write sparse image header failed\n");
return ZX_ERR_IO;
}
for (unsigned i = 0; i < image_.partition_count; i++) {
fvm::partition_descriptor_t partition = partitions_[i].descriptor;
header_length += sizeof(fvm::partition_descriptor_t);
if (write(fd_.get(), &partition, sizeof(fvm::partition_descriptor_t))
!= sizeof(fvm::partition_descriptor_t)) {
fprintf(stderr, "Write partition failed\n");
return ZX_ERR_IO;
}
for (unsigned j = 0; j < partition.extent_count; j++) {
fvm::extent_descriptor_t extent = partitions_[i].extents[j];
header_length += sizeof(fvm::extent_descriptor_t);
if (write(fd_.get(), &extent, sizeof(fvm::extent_descriptor_t))
!= sizeof(fvm::extent_descriptor_t)) {
fprintf(stderr, "Write extent failed\n");
return ZX_ERR_IO;
}
}
}
if (header_length != image_.header_length) {
fprintf(stderr, "Header length does not match!\n");
return ZX_ERR_INTERNAL;
}
zx_status_t status;
if ((status = PrepareWrite(extent_size_)) != ZX_OK) {
return status;
}
// Write each partition out to sparse file
for (unsigned i = 0; i < image_.partition_count; i++) {
fvm::partition_descriptor_t partition = partitions_[i].descriptor;
Format* format = partitions_[i].format.get();
vslice_info_t vslice_info;
// Write out each extent in the partition
for (unsigned j = 0; j < partition.extent_count; j++) {
if (format->GetVsliceRange(j, &vslice_info) != ZX_OK) {
fprintf(stderr, "Unable to access partition extent\n");
return ZX_ERR_OUT_OF_RANGE;
}
// Write out each block in the extent
for (unsigned k = 0; k < vslice_info.block_count; k++) {
if (format->FillBlock(vslice_info.block_offset + k) != ZX_OK) {
fprintf(stderr, "Failed to read block\n");
return ZX_ERR_IO;
}
if (WriteData(format->Data(), format->BlockSize()) != ZX_OK) {
fprintf(stderr, "Failed to write data to sparse file\n");
return ZX_ERR_IO;
}
}
}
}
if ((status = CompleteWrite()) != ZX_OK) {
return status;
}
struct stat s;
if (fstat(fd_.get(), &s) < 0) {
fprintf(stderr, "Failed to stat container\n");
return ZX_ERR_IO;
}
disk_size_ = s.st_size;
xprintf("Successfully wrote sparse data to disk.\n");
return ZX_OK;
}
size_t SparseContainer::SliceSize() const {
return image_.slice_size;
}
zx_status_t SparseContainer::AddPartition(const char* path, const char* type_name) {
fbl::unique_ptr<Format> format;
zx_status_t status;
if ((status = Format::Create(path, type_name, &format)) != ZX_OK) {
fprintf(stderr, "Failed to initialize partition\n");
return status;
}
if ((status = AllocatePartition(fbl::move(format))) != ZX_OK) {
fprintf(stderr, "Sparse partition allocation failed\n");
return status;
}
return ZX_OK;
}
zx_status_t SparseContainer::AllocatePartition(fbl::unique_ptr<Format> format) {
partition_info_t partition;
partition.descriptor.magic = fvm::kPartitionDescriptorMagic;
format->Type(partition.descriptor.type);
format->Name(reinterpret_cast<char*>(partition.descriptor.name));
partition.descriptor.extent_count = 0;
partition.descriptor.flags = flags_ & format->FlagMask();
image_.header_length += sizeof(fvm::partition_descriptor_t);
uint32_t part_index = image_.partition_count;
zx_status_t status;
if ((status = format->MakeFvmReady(SliceSize(), part_index)) != ZX_OK) {
return status;
}
partitions_.push_back(fbl::move(partition));
if (++image_.partition_count != partitions_.size()) {
fprintf(stderr, "Unexpected number of partitions\n");
return ZX_ERR_INTERNAL;
}
vslice_info_t vslice_info;
unsigned i = 0;
while ((status = format->GetVsliceRange(i++, &vslice_info)) == ZX_OK) {
if ((status = AllocateExtent(part_index,
vslice_info.vslice_start / format->BlocksPerSlice(),
vslice_info.slice_count,
vslice_info.block_count * format->BlockSize())) != ZX_OK) {
return status;
}
}
// This is expected if we have read all the other slices.
if (status != ZX_ERR_OUT_OF_RANGE) {
return status;
}
partitions_[part_index].format = fbl::move(format);
return ZX_OK;
}
zx_status_t SparseContainer::AllocateExtent(uint32_t part_index, uint64_t slice_start,
uint64_t slice_count, uint64_t extent_length) {
if (part_index >= image_.partition_count) {
fprintf(stderr, "Partition is not yet allocated\n");
return ZX_ERR_OUT_OF_RANGE;
}
partition_info_t* partition = &partitions_[part_index];
fvm::extent_descriptor_t extent;
extent.magic = fvm::kExtentDescriptorMagic;
extent.slice_start = slice_start;
extent.slice_count = slice_count;
extent.extent_length = extent_length;
partition->extents.push_back(extent);
if (partition->extents.size() != ++partition->descriptor.extent_count) {
fprintf(stderr, "Unexpected number of extents\n");
return ZX_ERR_INTERNAL;
}
image_.header_length += sizeof(fvm::extent_descriptor_t);
extent_size_ += extent_length;
dirty_ = true;
return ZX_OK;
}
zx_status_t SparseContainer::PrepareWrite(size_t max_len) {
if ((flags_ & fvm::kSparseFlagLz4) == 0) {
return ZX_OK;
}
return compression_.Setup(max_len);
}
zx_status_t SparseContainer::WriteData(const void* data, size_t length) {
if ((flags_ & fvm::kSparseFlagLz4) != 0) {
return compression_.Compress(data, length);
} else if (write(fd_.get(), data, length) != length) {
return ZX_ERR_IO;
}
return ZX_OK;
}
zx_status_t SparseContainer::CompleteWrite() {
if ((flags_ & fvm::kSparseFlagLz4) == 0) {
return ZX_OK;
}
zx_status_t status = compression_.Finish();
if (status != ZX_OK) {
return status;
}
if (write(fd_.get(), compression_.GetData(), compression_.GetLength())
!= compression_.GetLength()) {
return ZX_ERR_IO;
}
return ZX_OK;
}