| // Copyright 2018 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/minfs/transaction_limits.h" |
| |
| #include <zircon/assert.h> |
| #include <zircon/errors.h> |
| |
| #include <algorithm> |
| |
| #include <fbl/algorithm.h> |
| #include <fs/journal/format.h> |
| |
| #include "src/storage/minfs/format.h" |
| |
| namespace minfs { |
| |
| blk_t GetBlockBitmapBlocks(const Superblock& info) { |
| ZX_DEBUG_ASSERT(info.ino_block >= info.abm_block); |
| blk_t bitmap_blocks = info.ino_block - info.abm_block; |
| |
| if (info.flags & kMinfsFlagFVM) { |
| const blk_t kBlocksPerSlice = static_cast<blk_t>(info.slice_size / info.BlockSize()); |
| bitmap_blocks = info.abm_slices * kBlocksPerSlice; |
| } |
| |
| return bitmap_blocks; |
| } |
| |
| zx::status<blk_t> GetRequiredBlockCount(size_t offset, size_t length, uint32_t block_size) { |
| if (block_size != kMinfsBlockSize) { |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| |
| if (length == 0) { |
| // Return early if no data needs to be written. |
| return zx::ok(0); |
| } |
| |
| // Determine which range of direct blocks will be accessed given offset and length, |
| // and add to total. |
| blk_t first_direct = static_cast<blk_t>(offset / block_size); |
| blk_t last_direct = static_cast<blk_t>((offset + length - 1) / block_size); |
| blk_t reserve_blocks = last_direct - first_direct + 1; |
| |
| if (last_direct < kMinfsDirect) { |
| return zx::ok(reserve_blocks); |
| } |
| |
| // If direct blocks go into indirect range, adjust the indices accordingly. |
| first_direct = std::max(first_direct, kMinfsDirect) - kMinfsDirect; |
| last_direct -= kMinfsDirect; |
| |
| // Calculate indirect blocks containing first and last direct blocks, and add to total. |
| blk_t first_indirect = first_direct / kMinfsDirectPerIndirect; |
| blk_t last_indirect = last_direct / kMinfsDirectPerIndirect; |
| reserve_blocks += last_indirect - first_indirect + 1; |
| |
| if (last_indirect >= kMinfsIndirect) { |
| // If indirect blocks go into doubly indirect range, adjust the indices accordingly. |
| first_indirect = std::max(first_indirect, kMinfsIndirect) - kMinfsIndirect; |
| last_indirect -= kMinfsIndirect; |
| |
| // Calculate doubly indirect blocks containing first/last indirect blocks, |
| // and add to total |
| blk_t first_dindirect = first_indirect / kMinfsDirectPerIndirect; |
| blk_t last_dindirect = last_indirect / kMinfsDirectPerIndirect; |
| reserve_blocks += last_dindirect - first_dindirect + 1; |
| |
| if (last_dindirect >= kMinfsDoublyIndirect) { |
| // We cannot allocate blocks which exceed the doubly indirect range. |
| return zx::error(ZX_ERR_OUT_OF_RANGE); |
| } |
| } |
| |
| return zx::ok(reserve_blocks); |
| } |
| |
| TransactionLimits::TransactionLimits(const Superblock& info) : block_size_(info.BlockSize()) { |
| CalculateDataBlocks(); |
| CalculateIntegrityBlocks(GetBlockBitmapBlocks(info)); |
| } |
| |
| void TransactionLimits::CalculateDataBlocks() { |
| // If we ever increase the number of doubly indirect blocks, we will need to update this offset |
| // to be 1 byte before the end of the first doubly indirect block. |
| const blk_t offset = |
| (kMinfsDirect + (kMinfsIndirect * kMinfsDirectPerIndirect)) * BlockSize() - 1; |
| |
| // This calculation ignores the fact that directory size is capped at |kMinfsMaxDirectorySize|, |
| // because following that constraint makes it a little harder to predict where the most |
| // significant cross-block write would be. This means we may overestimate the maximum number of |
| // directory blocks by some amount, but this is better than an understimate. |
| blk_t max_directory_blocks = |
| GetRequiredBlockCount(offset, kMinfsMaxDirentSize, BlockSize()).value(); |
| |
| max_data_blocks_ = GetRequiredBlockCount(offset, kMaxWriteBytes, BlockSize()).value(); |
| |
| blk_t direct_blocks = (fbl::round_up(kMaxWriteBytes, BlockSize()) / BlockSize()) + 1; |
| blk_t max_indirect_blocks = max_data_blocks_ - direct_blocks; |
| |
| max_meta_data_blocks_ = std::max(max_directory_blocks, max_indirect_blocks); |
| } |
| |
| void TransactionLimits::CalculateIntegrityBlocks(blk_t block_bitmap_blocks) { |
| max_entry_data_blocks_ = kMaxSuperblockBlocks + kMaxInodeBitmapBlocks + block_bitmap_blocks + |
| kMaxInodeTableBlocks + max_meta_data_blocks_; |
| |
| // Ensure we have enough space to fit all the block numbers that may be updated in one |
| // transaction. This may spill over into multiple blocks. |
| blk_t header_blocks = 1; |
| if (max_entry_data_blocks_ > fs::kMaxBlockDescriptors) { |
| header_blocks += fbl::round_up((max_entry_data_blocks_ - fs::kMaxBlockDescriptors), |
| kMinfsDirectPerIndirect) / |
| kMinfsDirectPerIndirect; |
| } |
| |
| // For revocation records, we need to know the maximum number of metadata blocks within the |
| // data section of Minfs that can be deleted within one operation. This is either a directory |
| // vnode's maximum possible number of data blocks + indirect blocks, or a data vnode's maximum |
| // possible number of indirect blocks. |
| blk_t maximum_directory_blocks = |
| GetRequiredBlockCount(0, kMinfsMaxDirectorySize, BlockSize()).value(); |
| blk_t maximum_indirect_blocks = kMinfsIndirect + kMinfsDoublyIndirect * kMinfsDirectPerIndirect; |
| blk_t revocation_blocks = |
| fbl::round_up(std::max(maximum_directory_blocks, maximum_indirect_blocks), |
| kMinfsDirectPerIndirect) / |
| kMinfsDirectPerIndirect; |
| |
| blk_t commit_blocks = 1; |
| |
| max_entry_blocks_ = header_blocks + revocation_blocks + max_entry_data_blocks_ + commit_blocks; |
| min_integrity_blocks_ = max_entry_blocks_ + kJournalMetadataBlocks + kBackupSuperblockBlocks; |
| rec_integrity_blocks_ = std::max(min_integrity_blocks_, kDefaultJournalBlocks); |
| } |
| |
| } // namespace minfs |