| // 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/blobfs/host.h" |
| |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <lib/cksum.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <zircon/assert.h> |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <memory> |
| #include <new> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| |
| #include <digest/digest.h> |
| #include <digest/merkle-tree.h> |
| #include <digest/node-digest.h> |
| #include <fbl/algorithm.h> |
| #include <fbl/array.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/macros.h> |
| #include <fs-host/common.h> |
| #include <fs/journal/initializer.h> |
| #include <fs/transaction/transaction_handler.h> |
| #include <safemath/checked_math.h> |
| #include <safemath/safe_conversions.h> |
| |
| #include "src/storage/blobfs/blob-layout.h" |
| #include "src/storage/blobfs/common.h" |
| #include "src/storage/blobfs/compression-settings.h" |
| #include "src/storage/blobfs/compression/chunked.h" |
| #include "src/storage/blobfs/compression/compressor.h" |
| #include "src/storage/blobfs/compression/decompressor.h" |
| #include "src/storage/blobfs/format.h" |
| #include "src/storage/blobfs/fsck-host.h" |
| |
| using digest::Digest; |
| using digest::MerkleTreeCreator; |
| using digest::MerkleTreeVerifier; |
| |
| constexpr uint32_t kExtentCount = 5; |
| |
| namespace blobfs { |
| namespace { |
| |
| // TODO(markdittmer): Abstract choice of host compressor, decompressor and metadata flag to support |
| // choosing from multiple strategies. This has already been done in non-host code but host tools do |
| // not use |BlobCompressor| the same way. |
| using HostCompressor = ChunkedCompressor; |
| using HostDecompressor = ChunkedDecompressor; |
| |
| constexpr CompressionSettings kCompressionSettings = { |
| .compression_algorithm = CompressionAlgorithm::CHUNKED, |
| }; |
| |
| zx_status_t ReadBlockOffset(int fd, uint64_t bno, off_t offset, void* data) { |
| off_t off = offset + bno * kBlobfsBlockSize; |
| if (pread(fd, data, kBlobfsBlockSize, off) != kBlobfsBlockSize) { |
| FX_LOGS(ERROR) << "cannot read block " << bno; |
| return ZX_ERR_IO; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t WriteBlockOffset(int fd, const void* data, uint64_t block_count, off_t offset, |
| uint64_t block_number) { |
| off_t off = safemath::checked_cast<off_t>( |
| safemath::CheckAdd(offset, safemath::CheckMul(block_number, kBlobfsBlockSize).ValueOrDie()) |
| .ValueOrDie()); |
| size_t size = safemath::CheckMul(block_count, kBlobfsBlockSize).ValueOrDie(); |
| ssize_t ret; |
| auto udata = static_cast<const uint8_t*>(data); |
| while (size > 0) { |
| ret = pwrite(fd, udata, size, off); |
| if (ret < 0) { |
| perror("failed write"); |
| FX_LOGS(ERROR) << "cannot write block " << block_number << " size:" << size << " off:" << off; |
| return ZX_ERR_IO; |
| } |
| size -= ret; |
| off += ret; |
| udata += ret; |
| } |
| return ZX_OK; |
| } |
| |
| // From a buffer, create a merkle tree. |
| // |
| // Given a mapped blob at |blob_data| of length |length|, compute the |
| // Merkle digest and the output merkle tree as a uint8_t array. |
| zx_status_t buffer_create_merkle(const FileMapping& mapping, bool use_compact_format, |
| MerkleInfo* out_info) { |
| zx_status_t status; |
| MerkleTreeCreator mtc; |
| mtc.SetUseCompactFormat(use_compact_format); |
| if ((status = mtc.SetDataLength(mapping.length())) != ZX_OK) { |
| return status; |
| } |
| std::unique_ptr<uint8_t[]> merkle_tree; |
| size_t merkle_length = mtc.GetTreeLength(); |
| if (merkle_length > 0) { |
| merkle_tree.reset(new uint8_t[merkle_length]); |
| } |
| uint8_t root[digest::kSha256Length]; |
| if ((status = mtc.SetTree(merkle_tree.get(), merkle_length, root, digest::kSha256Length)) != |
| ZX_OK) { |
| return status; |
| } |
| if ((status = mtc.Append(mapping.data(), mapping.length())) != ZX_OK) { |
| return status; |
| } |
| out_info->digest = root; |
| out_info->merkle = std::move(merkle_tree); |
| out_info->merkle_length = merkle_length; |
| out_info->length = mapping.length(); |
| return ZX_OK; |
| } |
| |
| zx_status_t buffer_compress(const FileMapping& mapping, MerkleInfo* out_info) { |
| size_t max = HostCompressor::BufferMax(mapping.length()); |
| out_info->compressed_data.reset(new uint8_t[max]); |
| out_info->compressed = false; |
| |
| if (mapping.length() < kCompressionSizeThresholdBytes) { |
| return ZX_OK; |
| } |
| |
| zx_status_t status; |
| std::unique_ptr<HostCompressor> compressor; |
| size_t output_limit; |
| if ((status = HostCompressor::Create(kCompressionSettings, mapping.length(), &output_limit, |
| &compressor)) != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to initialize blobfs compressor: " << status; |
| return status; |
| } |
| if ((status = compressor->SetOutput(out_info->compressed_data.get(), max)) != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to initialize blobfs compressor: " << status; |
| return status; |
| } |
| |
| if ((status = compressor->Update(mapping.data(), mapping.length())) != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to update blobfs compressor: " << status; |
| return status; |
| } |
| |
| if ((status = compressor->End()) != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to complete blobfs compressor: " << status; |
| return status; |
| } |
| |
| if (fbl::round_up(compressor->Size(), kBlobfsBlockSize) < |
| fbl::round_up(mapping.length(), kBlobfsBlockSize)) { |
| out_info->compressed_length = compressor->Size(); |
| out_info->compressed = true; |
| } |
| |
| return ZX_OK; |
| } |
| |
| // Given a buffer (and pre-computed merkle tree), add the buffer as a |
| // blob in Blobfs. |
| zx_status_t blobfs_add_mapped_blob_with_merkle(Blobfs* bs, JsonRecorder* json_recorder, |
| const FileMapping& mapping, const MerkleInfo& info) { |
| ZX_ASSERT(mapping.length() == info.length); |
| const void* data; |
| |
| if (info.compressed) { |
| data = info.compressed_data.get(); |
| } else { |
| data = mapping.data(); |
| } |
| |
| auto blob_layout = BlobLayout::CreateFromSizes(GetBlobLayoutFormat(bs->Info()), info.length, |
| info.GetDataSize(), bs->GetBlockSize()); |
| if (blob_layout.is_error()) { |
| FX_LOGS(ERROR) << "Failed to create blob layout: " << blob_layout.status_value(); |
| return blob_layout.status_value(); |
| } |
| |
| // After we've pre-calculated all necessary information, actually add the |
| // blob to the filesystem itself. |
| static std::mutex add_blob_mutex_; |
| std::lock_guard<std::mutex> lock(add_blob_mutex_); |
| std::unique_ptr<InodeBlock> inode_block; |
| zx_status_t status; |
| if ((status = bs->NewBlob(info.digest, &inode_block)) != ZX_OK) { |
| FX_LOGS(ERROR) << "error: Failed to allocate a new blob"; |
| return status; |
| } |
| if (inode_block == nullptr) { |
| FX_LOGS(ERROR) << "error: No nodes available on blobfs image"; |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| Inode* inode = inode_block->GetInode(); |
| inode->blob_size = mapping.length(); |
| inode->block_count = blob_layout->TotalBlockCount(); |
| inode->header.flags |= |
| kBlobFlagAllocated | (info.compressed ? HostCompressor::InodeHeaderCompressionFlags() : 0); |
| |
| // TODO(smklein): Currently, host-side tools can only generate single-extent |
| // blobs. This should be fixed. |
| if (inode->block_count > kBlockCountMax) { |
| FX_LOGS(ERROR) << "error: Blobs larger than " << kBlockCountMax |
| << " blocks not yet implemented"; |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| size_t start_block = 0; |
| if ((status = bs->AllocateBlocks(inode->block_count, &start_block)) != ZX_OK) { |
| FX_LOGS(ERROR) << "error: No blocks available"; |
| return status; |
| } |
| |
| // TODO(smklein): This is hardcoded alongside the check against "kBlockCountMax" above. |
| if (inode->block_count > 0) { |
| inode->extents[0].SetStart(start_block); |
| inode->extents[0].SetLength(static_cast<BlockCountType>(inode->block_count)); |
| inode->extent_count = 1; |
| } else { |
| inode->extent_count = 0; |
| } |
| |
| if (json_recorder) { |
| json_recorder->Append(info.path.c_str(), info.digest.ToString().c_str(), info.length, |
| kBlobfsBlockSize * inode->block_count); |
| } |
| |
| if ((status = bs->WriteData(inode, info.merkle.get(), data, *blob_layout.value())) != ZX_OK) { |
| return status; |
| } |
| |
| if ((status = bs->WriteBitmap(inode->block_count, inode->extents[0].Start())) != ZX_OK) { |
| return status; |
| } |
| if ((status = bs->WriteNode(std::move(inode_block))) != ZX_OK) { |
| return status; |
| } |
| if ((status = bs->WriteInfo()) != ZX_OK) { |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| // Returns ZX_OK and copies blobfs info_block_t, which is a block worth of data containing |
| // superblock, into |out_info_block| if the block read from fd belongs to blobfs. |
| zx_status_t blobfs_load_info_block(const fbl::unique_fd& fd, info_block_t* out_info_block, |
| off_t start = 0, std::optional<off_t> end = std::nullopt) { |
| info_block_t info_block; |
| |
| if (ReadBlockOffset(fd.get(), 0, start, reinterpret_cast<void*>(info_block.block)) < 0) { |
| return ZX_ERR_IO; |
| } |
| uint64_t blocks; |
| zx_status_t status; |
| if ((status = GetBlockCount(fd.get(), &blocks)) != ZX_OK) { |
| FX_LOGS(ERROR) << "cannot find end of underlying device"; |
| return status; |
| } |
| |
| if (end && |
| ((blocks * kBlobfsBlockSize) < safemath::checked_cast<uint64_t>(end.value() - start))) { |
| FX_LOGS(ERROR) << "Invalid file size"; |
| return ZX_ERR_BAD_STATE; |
| } |
| if ((status = CheckSuperblock(&info_block.info, blocks)) != ZX_OK) { |
| FX_LOGS(ERROR) << "Info check failed"; |
| return status; |
| } |
| |
| memcpy(out_info_block, &info_block, sizeof(*out_info_block)); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t get_superblock(const fbl::unique_fd& fd, off_t start, std::optional<off_t> end, |
| Superblock* info) { |
| info_block_t info_block; |
| zx_status_t status; |
| |
| if ((status = blobfs_load_info_block(fd, &info_block, start, end)) != ZX_OK) { |
| return status; |
| } |
| |
| memcpy(info, &info_block.info, sizeof(info_block.info)); |
| return ZX_OK; |
| } |
| |
| } // namespace |
| |
| zx_status_t ReadBlock(int fd, uint64_t bno, void* data) { |
| off_t off = bno * kBlobfsBlockSize; |
| if (pread(fd, data, kBlobfsBlockSize, off) != kBlobfsBlockSize) { |
| FX_LOGS(ERROR) << "cannot read block " << bno; |
| return ZX_ERR_IO; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t WriteBlocks(int fd, uint64_t block_offset, uint64_t block_count, const void* data) { |
| if (WriteBlockOffset(fd, data, block_count, 0, block_offset) != ZX_OK) { |
| FX_LOGS(ERROR) << "cannot write blocks: " << block_count |
| << " at block offset: " << block_offset; |
| return ZX_ERR_IO; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t WriteBlock(int fd, uint64_t bno, const void* data) { |
| return WriteBlocks(fd, bno, 1, data); |
| } |
| |
| zx_status_t GetBlockCount(int fd, uint64_t* out) { |
| struct stat s; |
| if (fstat(fd, &s) < 0) { |
| return ZX_ERR_BAD_STATE; |
| } |
| *out = s.st_size / kBlobfsBlockSize; |
| return ZX_OK; |
| } |
| |
| int Mkfs(int fd, uint64_t block_count, const FilesystemOptions& options) { |
| Superblock info; |
| InitializeSuperblock(block_count, options, &info); |
| zx_status_t status = CheckSuperblock(&info, block_count); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to initialize superblock: " << status; |
| return -1; |
| } |
| uint64_t block_bitmap_blocks = BlockMapBlocks(info); |
| uint64_t node_map_blocks = NodeMapBlocks(info); |
| |
| RawBitmap block_bitmap; |
| if (block_bitmap.Reset(block_bitmap_blocks * kBlobfsBlockBits)) { |
| FX_LOGS(ERROR) << "Couldn't allocate blobfs block map"; |
| return -1; |
| } |
| if (block_bitmap.Shrink(info.data_block_count)) { |
| FX_LOGS(ERROR) << "Couldn't shrink blobfs block map"; |
| return -1; |
| } |
| |
| // Reserve first |kStartBlockMinimum| data blocks |
| block_bitmap.Set(0, kStartBlockMinimum); |
| |
| // All in-memory structures have been created successfully. Dump everything to disk. |
| // Initialize on-disk journal. |
| fs::WriteBlocksFn write_blocks_fn = [fd, &info](fbl::Span<const uint8_t> buffer, |
| uint64_t block_offset, uint64_t block_count) { |
| ZX_ASSERT((block_offset + block_count) <= JournalBlocks(info)); |
| ZX_ASSERT(buffer.size() >= (block_count * kBlobfsBlockSize)); |
| return WriteBlocks(fd, JournalStartBlock(info) + block_offset, block_count, buffer.data()); |
| }; |
| status = fs::MakeJournal(JournalBlocks(info), write_blocks_fn); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to write journal block"; |
| return -1; |
| } |
| |
| // Write the root block to disk. |
| static_assert(kBlobfsBlockSize == sizeof(info)); |
| if ((status = WriteBlock(fd, 0, &info)) != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to write Superblock"; |
| return -1; |
| } |
| |
| // Write allocation bitmap to disk. |
| if (WriteBlocks(fd, BlockMapStartBlock(info), block_bitmap_blocks, |
| block_bitmap.StorageUnsafe()->GetData()) != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to write blockmap block " << block_bitmap_blocks; |
| return -1; |
| } |
| |
| // Write node map to disk. |
| size_t map_length = node_map_blocks * kBlobfsBlockSize; |
| void* blocks = mmap(nullptr, map_length, PROT_READ, MAP_PRIVATE | MAP_ANON, -1, 0); |
| if (blocks == MAP_FAILED) { |
| FX_LOGS(ERROR) << "failed to map zeroes for inode map of size " << map_length; |
| return -1; |
| } |
| if (WriteBlocks(fd, NodeMapStartBlock(info), node_map_blocks, blocks) != ZX_OK) { |
| FX_LOGS(ERROR) << "failed writing inode map"; |
| munmap(blocks, map_length); |
| return -1; |
| } |
| if (munmap(blocks, map_length) != 0) { |
| FX_LOGS(ERROR) << "failed unmap inode map"; |
| return -1; |
| } |
| |
| FX_LOGS(DEBUG) << "mkfs success"; |
| return 0; |
| } |
| |
| zx_status_t UsedDataSize(const fbl::unique_fd& fd, uint64_t* out_size, off_t start, |
| std::optional<off_t> end) { |
| Superblock info; |
| zx_status_t status; |
| |
| if ((status = get_superblock(fd, start, end, &info)) != ZX_OK) { |
| return status; |
| } |
| |
| *out_size = info.alloc_block_count * info.block_size; |
| return ZX_OK; |
| } |
| |
| zx_status_t UsedInodes(const fbl::unique_fd& fd, uint64_t* out_inodes, off_t start, |
| std::optional<off_t> end) { |
| Superblock info; |
| zx_status_t status; |
| |
| if ((status = get_superblock(fd, start, end, &info)) != ZX_OK) { |
| return status; |
| } |
| |
| *out_inodes = info.alloc_inode_count; |
| return ZX_OK; |
| } |
| |
| zx_status_t UsedSize(const fbl::unique_fd& fd, uint64_t* out_size, off_t start, |
| std::optional<off_t> end) { |
| Superblock info; |
| zx_status_t status; |
| |
| if ((status = get_superblock(fd, start, end, &info)) != ZX_OK) { |
| return status; |
| } |
| |
| *out_size = (TotalNonDataBlocks(info) + info.alloc_block_count) * info.block_size; |
| return ZX_OK; |
| } |
| |
| zx_status_t blobfs_create(std::unique_ptr<Blobfs>* out, fbl::unique_fd fd) { |
| info_block_t info_block; |
| zx_status_t status; |
| |
| if ((status = blobfs_load_info_block(fd, &info_block)) != ZX_OK) { |
| return status; |
| } |
| |
| fbl::Array<size_t> extent_lengths(new size_t[kExtentCount], kExtentCount); |
| |
| extent_lengths[0] = BlockMapStartBlock(info_block.info) * kBlobfsBlockSize; |
| extent_lengths[1] = BlockMapBlocks(info_block.info) * kBlobfsBlockSize; |
| extent_lengths[2] = NodeMapBlocks(info_block.info) * kBlobfsBlockSize; |
| extent_lengths[3] = JournalBlocks(info_block.info) * kBlobfsBlockSize; |
| extent_lengths[4] = DataBlocks(info_block.info) * kBlobfsBlockSize; |
| |
| if ((status = Blobfs::Create(std::move(fd), 0, info_block, extent_lengths, out)) != ZX_OK) { |
| FX_LOGS(ERROR) << "mount failed; could not create blobfs"; |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t blobfs_create_sparse(std::unique_ptr<Blobfs>* out, fbl::unique_fd fd, off_t start, |
| off_t end, const fbl::Vector<size_t>& extent_vector) { |
| if (start >= end) { |
| FX_LOGS(ERROR) << "Insufficient space allocated"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (extent_vector.size() != kExtentCount) { |
| FX_LOGS(ERROR) << "Incorrect number of extents"; |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| info_block_t info_block; |
| zx_status_t status; |
| |
| if ((status = blobfs_load_info_block(fd, &info_block, start, end)) != ZX_OK) { |
| return status; |
| } |
| |
| fbl::Array<size_t> extent_lengths(new size_t[kExtentCount], kExtentCount); |
| |
| extent_lengths[0] = extent_vector[0]; |
| extent_lengths[1] = extent_vector[1]; |
| extent_lengths[2] = extent_vector[2]; |
| extent_lengths[3] = extent_vector[3]; |
| extent_lengths[4] = extent_vector[4]; |
| |
| if ((status = Blobfs::Create(std::move(fd), start, info_block, extent_lengths, out)) != ZX_OK) { |
| FX_LOGS(ERROR) << "mount failed; could not create blobfs"; |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t blobfs_preprocess(int data_fd, bool compress, BlobLayoutFormat blob_layout_format, |
| MerkleInfo* out_info) { |
| FileMapping mapping; |
| zx_status_t status = mapping.Map(data_fd); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| if ((status = buffer_create_merkle(mapping, ShouldUseCompactMerkleTreeFormat(blob_layout_format), |
| out_info)) != ZX_OK) { |
| return status; |
| } |
| |
| if (compress) { |
| status = buffer_compress(mapping, out_info); |
| } |
| |
| return status; |
| } |
| |
| zx_status_t blobfs_add_blob(Blobfs* bs, JsonRecorder* json_recorder, int data_fd) { |
| FileMapping mapping; |
| zx_status_t status = mapping.Map(data_fd); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // Calculate the actual Merkle tree. |
| MerkleInfo info; |
| status = buffer_create_merkle( |
| mapping, ShouldUseCompactMerkleTreeFormat(GetBlobLayoutFormat(bs->Info())), &info); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| return blobfs_add_mapped_blob_with_merkle(bs, json_recorder, mapping, info); |
| } |
| |
| zx_status_t blobfs_add_blob_with_merkle(Blobfs* bs, JsonRecorder* json_recorder, int data_fd, |
| const MerkleInfo& info) { |
| FileMapping mapping; |
| zx_status_t status = mapping.Map(data_fd); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| return blobfs_add_mapped_blob_with_merkle(bs, json_recorder, mapping, info); |
| } |
| |
| zx_status_t blobfs_fsck(fbl::unique_fd fd, off_t start, off_t end, |
| const fbl::Vector<size_t>& extent_lengths) { |
| std::unique_ptr<Blobfs> blob; |
| zx_status_t status; |
| if ((status = blobfs_create_sparse(&blob, std::move(fd), start, end, extent_lengths)) != ZX_OK) { |
| return status; |
| } |
| if ((status = Fsck(std::move(blob))) != ZX_OK) { |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| Blobfs::Blobfs(fbl::unique_fd fd, off_t offset, const info_block_t& info_block, |
| const fbl::Array<size_t>& extent_lengths) |
| : blockfd_(std::move(fd)), offset_(offset) { |
| ZX_ASSERT(extent_lengths.size() == kExtentCount); |
| memcpy(&info_block_, info_block.block, kBlobfsBlockSize); |
| cache_.bno = 0; |
| |
| block_map_start_block_ = extent_lengths[0] / kBlobfsBlockSize; |
| block_map_block_count_ = extent_lengths[1] / kBlobfsBlockSize; |
| node_map_start_block_ = block_map_start_block_ + block_map_block_count_; |
| node_map_block_count_ = extent_lengths[2] / kBlobfsBlockSize; |
| journal_start_block_ = node_map_start_block_ + node_map_block_count_; |
| journal_block_count_ = extent_lengths[3] / kBlobfsBlockSize; |
| data_start_block_ = journal_start_block_ + journal_block_count_; |
| data_block_count_ = extent_lengths[4] / kBlobfsBlockSize; |
| } |
| |
| zx_status_t Blobfs::Create(fbl::unique_fd blockfd_, off_t offset, const info_block_t& info_block, |
| const fbl::Array<size_t>& extent_lengths, std::unique_ptr<Blobfs>* out) { |
| zx_status_t status = CheckSuperblock(&info_block.info, TotalBlocks(info_block.info)); |
| if (status < 0) { |
| FX_LOGS(ERROR) << "Check info failure"; |
| return status; |
| } |
| |
| ZX_ASSERT(extent_lengths.size() == kExtentCount); |
| |
| for (unsigned i = 0; i < 3; i++) { |
| if (extent_lengths[i] % kBlobfsBlockSize) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| |
| auto fs = |
| std::unique_ptr<Blobfs>(new Blobfs(std::move(blockfd_), offset, info_block, extent_lengths)); |
| |
| if ((status = fs->LoadBitmap()) < 0) { |
| FX_LOGS(ERROR) << "Failed to load bitmaps"; |
| return status; |
| } |
| |
| *out = std::move(fs); |
| return ZX_OK; |
| } |
| |
| zx_status_t Blobfs::LoadBitmap() { |
| zx_status_t status; |
| if ((status = block_map_.Reset(block_map_block_count_ * kBlobfsBlockBits)) != ZX_OK) { |
| return status; |
| } |
| if ((status = block_map_.Shrink(info_.data_block_count)) != ZX_OK) { |
| return status; |
| } |
| const void* bmstart = block_map_.StorageUnsafe()->GetData(); |
| |
| for (size_t n = 0; n < block_map_block_count_; n++) { |
| void* bmdata = fs::GetBlock(kBlobfsBlockSize, bmstart, n); |
| |
| if (n >= node_map_start_block_) { |
| memset(bmdata, 0, kBlobfsBlockSize); |
| } else if ((status = ReadBlock(block_map_start_block_ + n)) != ZX_OK) { |
| return status; |
| } else { |
| memcpy(bmdata, cache_.blk, kBlobfsBlockSize); |
| } |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Blobfs::NewBlob(const Digest& digest, std::unique_ptr<InodeBlock>* out) { |
| size_t ino = info_.inode_count; |
| |
| for (size_t i = 0; i < info_.inode_count; ++i) { |
| size_t bno = (i / kBlobfsInodesPerBlock) + node_map_start_block_; |
| |
| zx_status_t status; |
| if ((i % kBlobfsInodesPerBlock) == 0) { |
| if ((status = ReadBlock(bno)) != ZX_OK) { |
| return status; |
| } |
| } |
| |
| auto iblk = reinterpret_cast<const Inode*>(cache_.blk); |
| auto observed_inode = &iblk[i % kBlobfsInodesPerBlock]; |
| if (observed_inode->header.IsAllocated() && !observed_inode->header.IsExtentContainer()) { |
| if (digest == observed_inode->merkle_root_hash) { |
| return ZX_ERR_ALREADY_EXISTS; |
| } |
| } else if (ino >= info_.inode_count) { |
| // If |ino| has not already been set to a valid value, set it to the |
| // first free value we find. |
| // We still check all the remaining inodes to avoid adding a duplicate blob. |
| ino = i; |
| } |
| } |
| |
| if (ino >= info_.inode_count) { |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| size_t bno = (ino / kBlobfsInodesPerBlock) + NodeMapStartBlock(info_); |
| zx_status_t status; |
| if ((status = ReadBlock(bno)) != ZX_OK) { |
| return status; |
| } |
| |
| Inode* inodes = reinterpret_cast<Inode*>(cache_.blk); |
| |
| std::unique_ptr<InodeBlock> ino_block( |
| new InodeBlock(bno, &inodes[ino % kBlobfsInodesPerBlock], digest)); |
| |
| dirty_ = true; |
| info_.alloc_inode_count++; |
| *out = std::move(ino_block); |
| return ZX_OK; |
| } |
| |
| zx_status_t Blobfs::AllocateBlocks(size_t nblocks, size_t* blkno_out) { |
| zx_status_t status; |
| if ((status = block_map_.Find(false, 0, block_map_.size(), nblocks, blkno_out)) != ZX_OK) { |
| return status; |
| } |
| if ((status = block_map_.Set(*blkno_out, *blkno_out + nblocks)) != ZX_OK) { |
| return status; |
| } |
| |
| info_.alloc_block_count += nblocks; |
| return ZX_OK; |
| } |
| |
| zx_status_t Blobfs::WriteBitmap(size_t nblocks, size_t start_block) { |
| uint64_t block_bitmap_start_block = start_block / kBlobfsBlockBits; |
| uint64_t block_bitmap_end_block = |
| fbl::round_up(start_block + nblocks, kBlobfsBlockBits) / kBlobfsBlockBits; |
| const void* bmstart = block_map_.StorageUnsafe()->GetData(); |
| const void* data = fs::GetBlock(kBlobfsBlockSize, bmstart, block_bitmap_start_block); |
| uint64_t absolute_block_number = block_map_start_block_ + block_bitmap_start_block; |
| uint64_t block_count = block_bitmap_end_block - block_bitmap_start_block; |
| return WriteBlocks(absolute_block_number, block_count, data); |
| } |
| |
| zx_status_t Blobfs::WriteNode(std::unique_ptr<InodeBlock> ino_block) { |
| if (ino_block->GetBno() != cache_.bno) { |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| |
| dirty_ = false; |
| return WriteBlock(cache_.bno, cache_.blk); |
| } |
| |
| zx_status_t Blobfs::WriteData(Inode* inode, const void* merkle_data, const void* blob_data, |
| const BlobLayout& blob_layout) { |
| if (blob_layout.TotalBlockCount() == 0) { |
| // Nothing to write. |
| return ZX_OK; |
| } |
| // Allocate a new buffer to hold both the data and Merkle tree together. The data and Merkle tree |
| // may not be block multiples in size which makes writing them separately in terms of blocks |
| // difficult, also the data and Merkle tree may share a block. Creating a new buffer to hold both |
| // uses more memory but makes writing the blob significantly easier. |
| uint64_t block_size = GetBlockSize(); |
| uint64_t buf_size = block_size * blob_layout.TotalBlockCount(); |
| auto buf = std::make_unique<uint8_t[]>(blob_layout.TotalBlockCount() * GetBlockSize()); |
| // Zero the entire buffer instead of trying to calculate where the data and Merkle tree won't be. |
| memset(buf.get(), 0, buf_size); |
| |
| // Copy the data to the buffer. |
| uint64_t data_offset = block_size * blob_layout.DataBlockOffset(); |
| memcpy(buf.get() + data_offset, blob_data, blob_layout.DataSizeUpperBound()); |
| |
| // |merkle_data| will be null when the blob size is less than or equal to the Merkle tree node |
| // size. |
| if (merkle_data) { |
| // Copy the Merkle tree to the buffer. |
| uint64_t merkle_offset = block_size * blob_layout.MerkleTreeBlockOffset() + |
| blob_layout.MerkleTreeOffsetWithinBlockOffset(); |
| memcpy(buf.get() + merkle_offset, merkle_data, blob_layout.MerkleTreeSize()); |
| } |
| |
| zx_status_t status; |
| uint32_t blob_start_block = data_start_block_ + inode->extents[0].Start(); |
| if ((status = WriteBlocks(blob_start_block, blob_layout.TotalBlockCount(), buf.get())) != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to write a blob: " << status; |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Blobfs::WriteInfo() { return WriteBlock(0, info_block_); } |
| |
| zx_status_t Blobfs::ReadBlock(size_t bno) { |
| if (dirty_) { |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| |
| zx_status_t status; |
| if ((cache_.bno != bno) && |
| ((status = ReadBlockOffset(blockfd_.get(), bno, offset_, &cache_.blk)) != ZX_OK)) { |
| return status; |
| } |
| |
| cache_.bno = bno; |
| return ZX_OK; |
| } |
| |
| zx_status_t Blobfs::WriteBlocks(size_t block_number, uint64_t block_count, const void* data) { |
| return WriteBlockOffset(blockfd_.get(), data, block_count, offset_, block_number); |
| } |
| |
| zx_status_t Blobfs::WriteBlock(size_t bno, const void* data) { |
| return WriteBlockOffset(blockfd_.get(), data, 1, offset_, bno); |
| } |
| |
| zx_status_t Blobfs::ResetCache() { |
| if (dirty_) { |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| |
| if (cache_.bno != 0) { |
| memset(cache_.blk, 0, kBlobfsBlockSize); |
| cache_.bno = 0; |
| } |
| return ZX_OK; |
| } |
| |
| zx::status<InodePtr> Blobfs::GetNode(uint32_t index) { |
| if (index >= info_.inode_count) { |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| size_t bno = node_map_start_block_ + index / kBlobfsInodesPerBlock; |
| if (zx_status_t status = ReadBlock(bno); status != ZX_OK) { |
| return zx::error(status); |
| } |
| |
| auto iblock = reinterpret_cast<Inode*>(cache_.blk); |
| return zx::ok(InodePtr(&iblock[index % kBlobfsInodesPerBlock], InodePtrDeleter(this))); |
| } |
| |
| fit::result<std::vector<uint8_t>, std::string> Blobfs::LoadAndVerifyBlob(Inode& inode) { |
| size_t blob_start_block = data_start_block_ + inode.extents[0].Start(); |
| uint32_t block_size = GetBlockSize(); |
| zx_status_t status; |
| auto make_error = [&](std::string error) { |
| digest::Digest digest(inode.merkle_root_hash); |
| auto digest_str = digest.ToString(); |
| return fit::error("Blob with merkle root hash of " + |
| std::string(digest_str.data(), digest_str.length()) + |
| " had errors. More specifically: " + error); |
| }; |
| |
| auto blob_layout = |
| blobfs::BlobLayout::CreateFromInode(GetBlobLayoutFormat(Info()), inode, block_size); |
| if (blob_layout.is_error()) { |
| return make_error("Failed to create blob layout with status " + |
| std::to_string(blob_layout.status_value())); |
| } |
| |
| // Read in the Merkle tree. |
| uint32_t merkle_tree_block_count = blob_layout->MerkleTreeBlockCount(); |
| uint32_t merkle_tree_block_offset = blob_layout->MerkleTreeBlockOffset(); |
| std::vector<uint8_t> merkle_tree_blocks(blob_layout->MerkleTreeBlockAlignedSize(), 0); |
| for (uint32_t block = 0; block < merkle_tree_block_count; ++block) { |
| ReadBlock(blob_start_block + merkle_tree_block_offset + block); |
| memcpy(&merkle_tree_blocks[block * block_size], cache_.blk, block_size); |
| } |
| |
| // Read in the data. |
| uint32_t data_block_count = blob_layout->DataBlockCount(); |
| uint32_t data_block_offset = blob_layout->DataBlockOffset(); |
| std::vector<uint8_t> data_blocks(blob_layout->DataBlockAlignedSize(), 0); |
| for (uint32_t block = 0; block < data_block_count; ++block) { |
| ReadBlock(blob_start_block + data_block_offset + block); |
| memcpy(&data_blocks[block * block_size], cache_.blk, block_size); |
| } |
| |
| // Decompress the data if necessary. |
| if (inode.header.flags & HostCompressor::InodeHeaderCompressionFlags()) { |
| size_t file_size = inode.blob_size; |
| std::vector<uint8_t> uncompressed_data(file_size, 0); |
| HostDecompressor decompressor; |
| if ((status = decompressor.Decompress(uncompressed_data.data(), &file_size, data_blocks.data(), |
| blob_layout->DataSizeUpperBound())) != ZX_OK) { |
| return make_error("Failed to decompress with status " + std::to_string(status)); |
| } |
| if (file_size != inode.blob_size) { |
| return make_error("Decompressed blob size of " + std::to_string(file_size) + |
| " mismatch with blob inode expected size of " + |
| std::to_string(inode.blob_size)); |
| } |
| // Replace the compressed data with the uncompressed data. |
| data_blocks = std::move(uncompressed_data); |
| } |
| |
| // Verify the contents of the blob. |
| uint8_t* merkle_tree_ptr = |
| merkle_tree_blocks.empty() |
| ? nullptr |
| : &merkle_tree_blocks[blob_layout->MerkleTreeOffsetWithinBlockOffset()]; |
| MerkleTreeVerifier mtv; |
| mtv.SetUseCompactFormat(blobfs::ShouldUseCompactMerkleTreeFormat(blob_layout->Format())); |
| if ((status = mtv.SetDataLength(inode.blob_size)) != ZX_OK || |
| (status = mtv.SetTree(merkle_tree_ptr, mtv.GetTreeLength(), inode.merkle_root_hash, |
| sizeof(inode.merkle_root_hash))) != ZX_OK || |
| (status = mtv.Verify(data_blocks.data(), inode.blob_size, 0)) != ZX_OK) { |
| return make_error("Verification failed with status " + std::to_string(status)); |
| } |
| |
| // Remove trailing block alignment. |
| data_blocks.resize(inode.blob_size, 0); |
| |
| return fit::ok(std::move(data_blocks)); |
| } |
| |
| zx_status_t Blobfs::LoadAndVerifyBlob(uint32_t node_index) { |
| auto inode_ptr = GetNode(node_index); |
| if (inode_ptr.is_error()) { |
| return inode_ptr.status_value(); |
| } |
| Inode inode = *inode_ptr.value(); |
| auto load_result = LoadAndVerifyBlob(inode); |
| return load_result.is_ok() ? ZX_OK : ZX_ERR_INTERNAL; |
| } |
| |
| uint32_t Blobfs::GetBlockSize() const { return Info().block_size; } |
| |
| fit::result<void, std::string> Blobfs::VisitBlobs(BlobVisitor visitor) { |
| for (uint64_t inode_index = 0, allocated_nodes = 0; |
| inode_index < info_.inode_count && allocated_nodes < info_.alloc_inode_count; |
| ++inode_index) { |
| auto inode_ptr = GetNode(inode_index); |
| if (inode_ptr.is_error()) { |
| return fit::error("Failed to retrieve inode."); |
| } |
| if (!inode_ptr->header.IsAllocated()) { |
| continue; |
| } |
| |
| // Required copy to preven additional calls to ReadBlock or GetNode to replace the contents |
| // of |cache_.blk| where inode_ptr comes from. |
| Inode inode = *inode_ptr.value(); |
| allocated_nodes++; |
| auto load_result = LoadAndVerifyBlob(inode); |
| if (load_result.is_error()) { |
| return load_result.take_error_result(); |
| } |
| BlobView view = { |
| .merkle_hash = fbl::Span<const uint8_t>(inode.merkle_root_hash), |
| .blob_contents = load_result.value(), |
| }; |
| |
| auto visitor_result = visitor(view); |
| if (visitor_result.is_error()) { |
| return visitor_result.take_error_result(); |
| } |
| } |
| return fit::ok(); |
| } |
| |
| fit::result<void, std::string> ExportBlobs(int output_dir, Blobfs& fs) { |
| return fs.VisitBlobs([output_dir](Blobfs::BlobView view) -> fit::result<void, std::string> { |
| uint8_t hash[digest::kSha256Length]; |
| memcpy(hash, view.merkle_hash.data(), digest::kSha256Length); |
| auto blob_name = digest::Digest(hash).ToString(); |
| fbl::unique_fd file(openat(output_dir, blob_name.c_str(), O_CREAT | O_RDWR, 0644)); |
| if (!file.is_valid()) { |
| return fit::error( |
| "Failed to create blob file" + std::string(blob_name.c_str()) + |
| "(merkle root digest) in output dir. More specifically: " + strerror(errno)); |
| } |
| |
| size_t written_bytes = 0; |
| int write_result = 0; |
| while (written_bytes < view.blob_contents.size()) { |
| write_result = write(file.get(), &view.blob_contents[written_bytes], |
| view.blob_contents.size() - written_bytes); |
| if (write_result < 0) { |
| return fit::error( |
| "Failed to write blob " + std::string(blob_name.c_str()) + |
| "(merkle root digest) contents in output file. More specifically: " + strerror(errno)); |
| } |
| written_bytes += write_result; |
| } |
| |
| return fit::ok(); |
| }); |
| } |
| |
| zx::status<std::unique_ptr<Superblock>> Blobfs::ReadBackupSuperblock() { |
| if (zx_status_t status = ReadBlock(kFVMBackupSuperblockOffset); status != ZX_OK) { |
| return zx::error(status); |
| } |
| return zx::ok(std::make_unique<Superblock>(*reinterpret_cast<Superblock*>(cache_.blk))); |
| } |
| |
| } // namespace blobfs |