Author: bonefish Date: 2010-07-12 15:21:42 +0200 (Mon, 12 Jul 2010) New Revision: 37478 Changeset: http://dev.haiku-os.org/changeset/37478 Added: haiku/trunk/src/tests/system/kernel/file_corruption/fs/Transaction.cpp haiku/trunk/src/tests/system/kernel/file_corruption/fs/Transaction.h Modified: haiku/trunk/src/tests/system/kernel/file_corruption/checksumfs.h haiku/trunk/src/tests/system/kernel/file_corruption/fs/Block.h haiku/trunk/src/tests/system/kernel/file_corruption/fs/BlockAllocator.cpp haiku/trunk/src/tests/system/kernel/file_corruption/fs/BlockAllocator.h haiku/trunk/src/tests/system/kernel/file_corruption/fs/Directory.cpp haiku/trunk/src/tests/system/kernel/file_corruption/fs/Directory.h haiku/trunk/src/tests/system/kernel/file_corruption/fs/Jamfile haiku/trunk/src/tests/system/kernel/file_corruption/fs/Node.cpp haiku/trunk/src/tests/system/kernel/file_corruption/fs/Node.h haiku/trunk/src/tests/system/kernel/file_corruption/fs/Volume.cpp haiku/trunk/src/tests/system/kernel/file_corruption/fs/Volume.h haiku/trunk/src/tests/system/kernel/file_corruption/fs/checksumfs.cpp haiku/trunk/src/tests/system/kernel/file_corruption/fs/userland/Jamfile Log: * Added Transaction class which wraps a block cache transaction and performs all other operations required to roll back a transaction. Transactions are fully serialized -- due to limitations of our block cache and also to keep things simple. * Use a transaction for all write operations. * Implemented the directory entry management code (a simple tree algorithm). * Finished/implemented the FS hooks for directory entry lookup, directory iteration, creation, and removal. * Added non-persistent support for node access times. * Set the user and group IDs on node creation. * Added permission checks to several FS hooks. * BlockAllocator::_Free(): The number of freed blocks was subtracted from fFreeBlocks instead of added. Modified: haiku/trunk/src/tests/system/kernel/file_corruption/checksumfs.h =================================================================== --- haiku/trunk/src/tests/system/kernel/file_corruption/checksumfs.h 2010-07-12 11:51:41 UTC (rev 37477) +++ haiku/trunk/src/tests/system/kernel/file_corruption/checksumfs.h 2010-07-12 13:21:42 UTC (rev 37478) @@ -56,9 +56,17 @@ } _PACKED; +static const uint32 kCheckSumFSMaxDirEntryTreeDepth = 24; + +struct checksumfs_dir_entry_tree { + uint16 depth; +} _PACKED; + + struct checksumfs_dir_entry_block { uint16 entryCount; - uint16 nameEnds[0]; // end (in-block) offsets of the names, + uint16 nameEnds[0]; // end offsets of the names (relative to the + // start of the first name), // e.g. nameEnds[0] == length of first name // char names[]; // string of all (unterminated) names, // directly follows the nameEnds array Modified: haiku/trunk/src/tests/system/kernel/file_corruption/fs/Block.h =================================================================== --- haiku/trunk/src/tests/system/kernel/file_corruption/fs/Block.h 2010-07-12 11:51:41 UTC (rev 37477) +++ haiku/trunk/src/tests/system/kernel/file_corruption/fs/Block.h 2010-07-12 13:21:42 UTC (rev 37478) @@ -8,6 +8,7 @@ #include <fs_cache.h> +#include "Transaction.h" #include "Volume.h" @@ -25,30 +26,64 @@ Put(); } + void TransferFrom(Block& other) + { + Put(); + + fVolume = other.fVolume; + fData = other.fData; + fIndex = other.fIndex; + fWritable = other.fWritable; + + other.fVolume = NULL; + other.fData = NULL; + } + bool GetReadable(Volume* volume, uint64 blockIndex) { Put(); return _Init(volume, blockIndex, - block_cache_get(volume->BlockCache(), blockIndex)); + block_cache_get(volume->BlockCache(), blockIndex), false); } - bool GetWritable(Volume* volume, uint64 blockIndex) + bool GetWritable(Volume* volume, uint64 blockIndex, + Transaction& transaction) { Put(); return _Init(volume, blockIndex, - block_cache_get_writable(volume->BlockCache(), blockIndex, -1)); + block_cache_get_writable(volume->BlockCache(), blockIndex, + transaction.ID()), + true); } - bool GetZero(Volume* volume, uint64 blockIndex) + bool GetZero(Volume* volume, uint64 blockIndex, Transaction& transaction) { Put(); return _Init(volume, blockIndex, - block_cache_get_empty(volume->BlockCache(), blockIndex, -1)); + block_cache_get_empty(volume->BlockCache(), blockIndex, + transaction.ID()), + true); } + status_t MakeWritable(Transaction& transaction) + { + if (fVolume == NULL) + return B_BAD_VALUE; + if (fWritable) + return B_OK; + + status_t error = block_cache_make_writable(fVolume->BlockCache(), + fIndex, transaction.ID()); + if (error != B_OK) + return error; + + fWritable = true; + return B_OK; + } + void Put() { if (fVolume != NULL) { @@ -78,7 +113,8 @@ } private: - bool _Init(Volume* volume, uint64 blockIndex, const void* data) + bool _Init(Volume* volume, uint64 blockIndex, const void* data, + bool writable) { if (data == NULL) return false; @@ -86,6 +122,7 @@ fVolume = volume; fData = const_cast<void*>(data); fIndex = blockIndex; + fWritable = writable; return true; } @@ -95,6 +132,7 @@ Volume* fVolume; void* fData; uint64 fIndex; + bool fWritable; }; Modified: haiku/trunk/src/tests/system/kernel/file_corruption/fs/BlockAllocator.cpp =================================================================== --- haiku/trunk/src/tests/system/kernel/file_corruption/fs/BlockAllocator.cpp 2010-07-12 11:51:41 UTC (rev 37477) +++ haiku/trunk/src/tests/system/kernel/file_corruption/fs/BlockAllocator.cpp 2010-07-12 13:21:42 UTC (rev 37478) @@ -77,7 +77,7 @@ status_t -BlockAllocator::Initialize() +BlockAllocator::Initialize(Transaction& transaction) { status_t error = Init(kCheckSumFSSuperBlockOffset / B_PAGE_SIZE + 1, fTotalBlocks); @@ -93,7 +93,7 @@ // clear the block bitmap for (uint64 i = 0; i < fBitmapBlockCount; i++) { Block block; - if (!block.GetZero(fVolume, fBitmapBlock + i)) + if (!block.GetZero(fVolume, fBitmapBlock + i, transaction)) return B_ERROR; } @@ -102,8 +102,10 @@ uint32 partialBitmapBlock = fTotalBlocks % kBlocksPerBitmapBlock; if (partialBitmapBlock != 0) { Block block; - if (!block.GetZero(fVolume, fBitmapBlock + fBitmapBlockCount - 1)) + if (!block.GetZero(fVolume, fBitmapBlock + fBitmapBlockCount - 1, + transaction)) { return B_ERROR; + } // set full uint32s uint32* bits = (uint32*)block.Data(); @@ -121,7 +123,7 @@ uint32 partialGroup = fTotalBlocks % kBlocksPerGroup; for (uint64 i = 0; i < fAllocationGroupCount; i++) { Block block; - if (!block.GetZero(fVolume, fAllocationGroupBlock + i)) + if (!block.GetZero(fVolume, fAllocationGroupBlock + i, transaction)) return B_ERROR; uint16* counts = (uint16*)block.Data(); @@ -143,7 +145,7 @@ } // mark all blocks we already use used - error = AllocateExactly(0, fBitmapBlock + fBitmapBlockCount); + error = AllocateExactly(0, fBitmapBlock + fBitmapBlockCount, transaction); if (error != B_OK) return error; @@ -159,8 +161,8 @@ status_t -BlockAllocator::Allocate(uint64 baseHint, uint64 count, uint64& _allocatedBase, - uint64& _allocatedCount) +BlockAllocator::Allocate(uint64 baseHint, uint64 count, + Transaction& transaction, uint64& _allocatedBase, uint64& _allocatedCount) { MutexLocker locker(fLock); dprintf("BlockAllocator::Allocate(%llu, %llu)\n", baseHint, count); @@ -172,29 +174,32 @@ baseHint = 0; // search from base hint to end - status_t error = _Allocate(baseHint, fTotalBlocks, count, &_allocatedBase, - _allocatedCount); + status_t error = _Allocate(baseHint, fTotalBlocks, count, transaction, + &_allocatedBase, _allocatedCount); if (error == B_OK || baseHint == 0) return error; // search from 0 to hint - return _Allocate(0, baseHint, count, &_allocatedBase, _allocatedCount); + return _Allocate(0, baseHint, count, transaction, &_allocatedBase, + _allocatedCount); } status_t -BlockAllocator::AllocateExactly(uint64 base, uint64 count) +BlockAllocator::AllocateExactly(uint64 base, uint64 count, + Transaction& transaction) { MutexLocker locker(fLock); dprintf("BlockAllocator::AllocateExactly(%llu, %llu)\n", base, count); uint64 allocated; - status_t error = _Allocate(base, fTotalBlocks, count, NULL, allocated); + status_t error = _Allocate(base, fTotalBlocks, count, transaction, NULL, + allocated); if (error != B_OK) return error; if (allocated < count) { - _Free(base, allocated); + _Free(base, allocated, transaction); return B_BUSY; } @@ -203,14 +208,23 @@ status_t -BlockAllocator::Free(uint64 base, uint64 count) +BlockAllocator::Free(uint64 base, uint64 count, Transaction& transaction) { MutexLocker locker(fLock); - return _Free(base, count); + return _Free(base, count, transaction); } +void +BlockAllocator::ResetFreeBlocks(uint64 count) +{ + MutexLocker locker(fLock); + + fFreeBlocks = count; +} + + /*! Allocates contiguous blocks. Might allocate fewer block than requested. If no block could be allocated @@ -231,7 +245,7 @@ */ status_t BlockAllocator::_Allocate(uint64 base, uint64 searchEnd, uint64 count, - uint64* _allocatedBase, uint64& _allocatedCount) + Transaction& transaction, uint64* _allocatedBase, uint64& _allocatedCount) { ASSERT(base <= fTotalBlocks); ASSERT(searchEnd <= fTotalBlocks); @@ -249,7 +263,7 @@ uint32 allocated; status_t error = _AllocateInGroup(base, searchEnd, toAllocate, - _allocatedBase, allocated); + transaction, _allocatedBase, allocated); if (error == B_OK) { fFreeBlocks -= toAllocate; @@ -281,8 +295,8 @@ while (remaining > 0 && base < searchEnd) { uint64 toAllocate = std::min(remaining, kBlocksPerGroup - groupOffset); uint32 allocated; - status_t error = _AllocateInGroup(base, searchEnd, toAllocate, NULL, - allocated); + status_t error = _AllocateInGroup(base, searchEnd, toAllocate, + transaction, NULL, allocated); if (error != B_OK) break; @@ -327,7 +341,7 @@ */ status_t BlockAllocator::_AllocateInGroup(uint64 base, uint64 searchEnd, uint32 count, - uint64* _allocatedBase, uint32& _allocatedCount) + Transaction& transaction, uint64* _allocatedBase, uint32& _allocatedCount) { dprintf("BlockAllocator::_AllocateInGroup(%llu, %lu)\n", base, count); ASSERT(count <= kBlocksPerGroup); @@ -338,7 +352,7 @@ Block block; if (!block.GetWritable(fVolume, - fAllocationGroupBlock + base / kBlocksPerGroup)) { + fAllocationGroupBlock + base / kBlocksPerGroup, transaction)) { return B_ERROR; } @@ -354,8 +368,8 @@ if (inBlockOffset != 0) { if (counts[blockIndex] < kBlocksPerBitmapBlock) { uint32 allocated; - if (_AllocateInBitmapBlock(base, count, _allocatedBase, - allocated) == B_OK) { + if (_AllocateInBitmapBlock(base, count, transaction, + _allocatedBase, allocated) == B_OK) { if (inBlockOffset + allocated < kBlocksPerBitmapBlock || allocated == remaining) { _allocatedCount = allocated; @@ -407,7 +421,7 @@ kBlocksPerBitmapBlock - inBlockOffset); uint32 allocated; - status_t error = _AllocateInBitmapBlock(base, toAllocate, + status_t error = _AllocateInBitmapBlock(base, toAllocate, transaction, _allocatedBase, allocated); if (error != B_OK) break; @@ -453,7 +467,7 @@ */ status_t BlockAllocator::_AllocateInBitmapBlock(uint64 base, uint32 count, - uint64* _allocatedBase, uint32& _allocatedCount) + Transaction& transaction, uint64* _allocatedBase, uint32& _allocatedCount) { dprintf("BlockAllocator::_AllocateInBitmapBlock(%llu, %lu)\n", base, count); ASSERT(count <= kBlocksPerBitmapBlock); @@ -461,7 +475,7 @@ Block block; if (!block.GetWritable(fVolume, - fBitmapBlock + base / kBlocksPerBitmapBlock)) { + fBitmapBlock + base / kBlocksPerBitmapBlock, transaction)) { return B_ERROR; } @@ -546,7 +560,7 @@ status_t -BlockAllocator::_Free(uint64 base, uint64 count) +BlockAllocator::_Free(uint64 base, uint64 count, Transaction& transaction) { if (count == 0) return B_OK; @@ -561,11 +575,11 @@ while (remaining > 0) { uint64 toFree = std::min(remaining, kBlocksPerGroup - groupOffset); - status_t error = _FreeInGroup(base, toFree); + status_t error = _FreeInGroup(base, toFree, transaction); if (error != B_OK) return error; - fFreeBlocks -= toFree; + fFreeBlocks += toFree; remaining -= toFree; base += toFree; groupOffset = 0; @@ -576,7 +590,8 @@ status_t -BlockAllocator::_FreeInGroup(uint64 base, uint32 count) +BlockAllocator::_FreeInGroup(uint64 base, uint32 count, + Transaction& transaction) { if (count == 0) return B_OK; @@ -587,7 +602,7 @@ Block block; if (!block.GetWritable(fVolume, - fAllocationGroupBlock + base / kBlocksPerGroup)) { + fAllocationGroupBlock + base / kBlocksPerGroup, transaction)) { return B_ERROR; } @@ -604,7 +619,7 @@ if (counts[blockIndex] + toFree > kBlocksPerBitmapBlock) return B_BAD_VALUE; - status_t error = _FreeInBitmapBlock(base, toFree); + status_t error = _FreeInBitmapBlock(base, toFree, transaction); if (error != B_OK) return error; @@ -620,7 +635,8 @@ status_t -BlockAllocator::_FreeInBitmapBlock(uint64 base, uint32 count) +BlockAllocator::_FreeInBitmapBlock(uint64 base, uint32 count, + Transaction& transaction) { dprintf("BlockAllocator::_FreeInBitmapBlock(%llu, %lu)\n", base, count); ASSERT(count <= kBlocksPerBitmapBlock); @@ -628,7 +644,7 @@ Block block; if (!block.GetWritable(fVolume, - fBitmapBlock + base / kBlocksPerBitmapBlock)) { + fBitmapBlock + base / kBlocksPerBitmapBlock, transaction)) { return B_ERROR; } Modified: haiku/trunk/src/tests/system/kernel/file_corruption/fs/BlockAllocator.h =================================================================== --- haiku/trunk/src/tests/system/kernel/file_corruption/fs/BlockAllocator.h 2010-07-12 11:51:41 UTC (rev 37477) +++ haiku/trunk/src/tests/system/kernel/file_corruption/fs/BlockAllocator.h 2010-07-12 13:21:42 UTC (rev 37478) @@ -9,6 +9,7 @@ #include <lock.h> +struct Transaction; struct Volume; @@ -22,29 +23,40 @@ uint64 FreeBlocks() const { return fFreeBlocks; } status_t Init(uint64 blockBitmap, uint64 freeBlocks); - status_t Initialize(); + status_t Initialize(Transaction& transaction); status_t Allocate(uint64 baseHint, uint64 count, + Transaction& transaction, uint64& _allocatedBase, uint64& _allocatedCount); status_t AllocateExactly(uint64 base, - uint64 count); - status_t Free(uint64 base, uint64 count); + uint64 count, Transaction& transaction); + status_t Free(uint64 base, uint64 count, + Transaction& transaction); + void ResetFreeBlocks(uint64 count); + // interface for Transaction only + private: status_t _Allocate(uint64 base, uint64 searchEnd, - uint64 count, uint64* _allocatedBase, + uint64 count, Transaction& transaction, + uint64* _allocatedBase, uint64& _allocatedCount); status_t _AllocateInGroup(uint64 base, uint64 searchEnd, - uint32 count, uint64* _allocatedBase, + uint32 count, Transaction& transaction, + uint64* _allocatedBase, uint32& _allocatedCount); status_t _AllocateInBitmapBlock(uint64 base, - uint32 count, uint64* _allocatedBase, + uint32 count, Transaction& transaction, + uint64* _allocatedBase, uint32& _allocatedCount); - status_t _Free(uint64 base, uint64 count); - status_t _FreeInGroup(uint64 base, uint32 count); - status_t _FreeInBitmapBlock(uint64 base, uint32 count); + status_t _Free(uint64 base, uint64 count, + Transaction& transaction); + status_t _FreeInGroup(uint64 base, uint32 count, + Transaction& transaction); + status_t _FreeInBitmapBlock(uint64 base, uint32 count, + Transaction& transaction); private: mutex fLock; @@ -60,9 +72,10 @@ class AllocatedBlock { public: - AllocatedBlock(BlockAllocator* allocator) + AllocatedBlock(BlockAllocator* allocator, Transaction& transaction) : fAllocator(allocator), + fTransaction(transaction), fIndex(0) { } @@ -70,7 +83,7 @@ ~AllocatedBlock() { if (fIndex > 0) - fAllocator->Free(fIndex, 1); + fAllocator->Free(fIndex, 1, fTransaction); } uint64 Index() const @@ -81,7 +94,8 @@ status_t Allocate(uint64 baseHint = 0) { uint64 allocatedBlocks; - status_t error = fAllocator->Allocate(0, 1, fIndex, allocatedBlocks); + status_t error = fAllocator->Allocate(0, 1, fTransaction, fIndex, + allocatedBlocks); if (error != B_OK) fIndex = 0; return error; @@ -96,6 +110,7 @@ private: BlockAllocator* fAllocator; + Transaction& fTransaction; uint64 fIndex; }; Modified: haiku/trunk/src/tests/system/kernel/file_corruption/fs/Directory.cpp =================================================================== --- haiku/trunk/src/tests/system/kernel/file_corruption/fs/Directory.cpp 2010-07-12 11:51:41 UTC (rev 37477) +++ haiku/trunk/src/tests/system/kernel/file_corruption/fs/Directory.cpp 2010-07-12 13:21:42 UTC (rev 37478) @@ -6,7 +6,1434 @@ #include "Directory.h" +#include <algorithm> +#include <new> +#include <AutoDeleter.h> + +#include "Block.h" +#include "BlockAllocator.h" +#include "DebugSupport.h" + + +class DirEntryBlock { +public: + DirEntryBlock(); + DirEntryBlock( + checksumfs_dir_entry_block* entryBlock, + size_t entryBlockSize); + + void SetTo(checksumfs_dir_entry_block* entryBlock, + size_t entryBlockSize); + + inline int32 EntryCount() const; + inline size_t BytesUsedFor(int32 entryCount) const; + inline size_t BytesUsed() const; + inline size_t FreeSpace() const; + + inline uint64 BlockIndexAt(int32 index) const; + const char* NameAt(int32 index, size_t& _nameLength) const; + + int32 FindInsertionIndex(const char* name, + size_t nameLength, bool& _exactMatch) const; + + int32 FindSplitIndex(int32 index, + size_t bytesNeeded) const; + + void InsertEntry(int32 index, const char* name, + size_t nameLength, uint64 blockIndex); + void ReplaceEntryName(int32 index, const char* name, + size_t nameLength); + void RemoveEntry(int32 index); + + void SplitBlock(int32 splitIndex, + DirEntryBlock& other); + + bool Check() const; + +private: + checksumfs_dir_entry_block* fBlock; + size_t fBlockSize; +}; + + +class DirEntryTree { +public: + DirEntryTree(Directory* directory); + + status_t LookupEntry(const char* name, + uint64& _blockIndex); + status_t LookupNextEntry(const char* name, + char* foundName, size_t& _foundNameLength, + uint64& _blockIndex); + + status_t InsertEntry(const char* name, uint64 blockIndex, + Transaction& transaction); + status_t RemoveEntry(const char* name, + Transaction& transaction); + + bool Check(); + +private: + struct LevelInfo { + Block block; + DirEntryBlock entryBlock; + int32 index; + bool exactMatch; + }; + +private: + status_t _InitReadOnly(); + status_t _InitWritable(Transaction& transaction); + status_t _InitCommon(); + + status_t _UpdateOrInsertKey(LevelInfo* infos, + int32 level, const char* name, + size_t nameLength, uint64 blockIndex, + bool insertKey, Transaction& transaction); + + status_t _InsertEntryIncrementDepth(LevelInfo* infos, + Transaction& transaction); + status_t _InsertEntrySplitBlock(int32 level, + LevelInfo& info, size_t needed, + Transaction& transaction, Block& newBlock, + int32& _splitIndex); + + bool _Check(int32 level, uint64 blockIndex, + const char* key, size_t keyLength, + DirEntryBlock& entryBlock); + + inline uint16 _Depth() const { return fTree->depth; } + +private: + Directory* fDirectory; + Block fRootBlock; + checksumfs_dir_entry_tree* fTree; + checksumfs_dir_entry_block* fRootEntryBlock; + size_t fRootEntryBlockSize; +}; + + +// #pragma mark - + + +static int +compare_names(const char* a, size_t lengthA, const char* b, size_t lengthB) +{ + int cmp = strncmp(a, b, std::min(lengthA, lengthB)); + if (cmp != 0) + return cmp; + + return (int)lengthA - (int)lengthB; + // assumes we don't overflow 31 bits +} + + +// #pragma mark - DirEntryBlock + + +DirEntryBlock::DirEntryBlock() + : + fBlock(NULL), + fBlockSize(0) +{ +} + + +DirEntryBlock::DirEntryBlock(checksumfs_dir_entry_block* entryBlock, + size_t entryBlockSize) + : + fBlock(entryBlock), + fBlockSize(entryBlockSize) +{ +} + + +void +DirEntryBlock::SetTo(checksumfs_dir_entry_block* entryBlock, + size_t entryBlockSize) +{ + fBlock = entryBlock; + fBlockSize = entryBlockSize; +} + + +int32 +DirEntryBlock::EntryCount() const +{ + return fBlock->entryCount; +} + + +size_t +DirEntryBlock::BytesUsedFor(int32 entryCount) const +{ + if (entryCount == 0) + return sizeof(*fBlock); + return sizeof(*fBlock) + 10 * entryCount + + fBlock->nameEnds[entryCount - 1]; +} + + +size_t +DirEntryBlock::BytesUsed() const +{ + return BytesUsedFor(EntryCount()); +} + + +size_t +DirEntryBlock::FreeSpace() const +{ + return fBlockSize - BytesUsed(); +} + + +uint64 +DirEntryBlock::BlockIndexAt(int32 index) const +{ + uint64* blockIndices + = (uint64*)((uint8*)fBlock + fBlockSize) - 1; + return blockIndices[-index]; +} + + +const char* +DirEntryBlock::NameAt(int32 index, size_t& _nameLength) const +{ + int32 entryCount = EntryCount(); + if (index < 0 || index >= entryCount) + return NULL; + + uint32 nameOffset = index > 0 ? fBlock->nameEnds[index - 1] : 0; + _nameLength = fBlock->nameEnds[index] - nameOffset; + return (const char*)(fBlock->nameEnds + entryCount) + nameOffset; +} + + +int32 +DirEntryBlock::FindInsertionIndex(const char* name, size_t nameLength, + bool& _exactMatch) const +{ + int32 entryCount = EntryCount(); + if (entryCount == 0) { + _exactMatch = false; + return 0; + } + + const char* entryNames = (char*)(fBlock->nameEnds + entryCount); + uint32 nameOffset = 0; + + int32 index = 0; + int cmp = -1; + + // TODO: Binary search! + for (; index < entryCount; index++) { + const char* entryName = entryNames + nameOffset; + size_t entryNameLength = fBlock->nameEnds[index] - nameOffset; + + cmp = compare_names(entryName, entryNameLength, name, nameLength); + if (cmp >= 0) + break; + + nameOffset += entryNameLength; + } + + _exactMatch = cmp == 0; + return index; +} + + +/*! Finds a good split index for an insertion of \a bytesNeeded bytes at + index \a index. +*/ +int32 +DirEntryBlock::FindSplitIndex(int32 index, size_t bytesNeeded) const +{ + size_t splitSize = (BytesUsed() + bytesNeeded) / 2; + + int32 entryCount = EntryCount(); + for (int32 i = 0; i < entryCount; i++) { + size_t bytesUsed = BytesUsedFor(i + 1); + if (i == index) + bytesUsed += bytesNeeded; + if (bytesUsed > splitSize) + return i; + } + + // This should never happen. + return entryCount; +} + + +void +DirEntryBlock::InsertEntry(int32 index, const char* name, size_t nameLength, + uint64 blockIndex) +{ + uint64* blockIndices = (uint64*)((uint8*)fBlock + fBlockSize) - 1; + int32 entryCount = fBlock->entryCount; + char* entryNames = (char*)(fBlock->nameEnds + entryCount); + + uint32 nameOffset = index == 0 ? 0 : fBlock->nameEnds[index - 1]; + uint32 lastNameEnd = entryCount == 0 + ? 0 : fBlock->nameEnds[entryCount - 1]; + + if (index < entryCount) { + // make room in the block indices array + memmove(&blockIndices[-entryCount], &blockIndices[1 - entryCount], + 8 * (entryCount - index)); + + // make room in the name array -- we also move 2 bytes more for the + // new entry in the nameEnds array + memmove(entryNames + nameOffset + nameLength + 2, + entryNames + nameOffset, lastNameEnd - nameOffset); + + // move the names < index by 2 bytes + if (index > 0) + memmove(entryNames + 2, entryNames, nameOffset); + + // move and update the nameEnds entries > index + for (int32 i = entryCount; i > index; i--) + fBlock->nameEnds[i] = fBlock->nameEnds[i - 1] + nameLength; + } else if (entryCount > 0) { + // the nameEnds array grows -- move all names + memmove(entryNames + 2, entryNames, lastNameEnd); + } + + // we have made room -- insert the entry + entryNames += 2; + memcpy(entryNames + nameOffset, name, nameLength); + fBlock->nameEnds[index] = nameOffset + nameLength; + blockIndices[-index] = blockIndex; + fBlock->entryCount++; +ASSERT(Check()); +} + + +void +DirEntryBlock::ReplaceEntryName(int32 index, const char* name, + size_t nameLength) +{ + int32 entryCount = fBlock->entryCount; + char* entryNames = (char*)(fBlock->nameEnds + entryCount); + + ASSERT(index >= 0 && index < entryCount); + + uint32 nameOffset = index == 0 ? 0 : fBlock->nameEnds[index - 1]; + uint32 oldNameLength = fBlock->nameEnds[index] - nameOffset; + uint32 lastNameEnd = fBlock->nameEnds[entryCount - 1]; + + if (oldNameLength != nameLength) { + int32 lengthDiff = (int32)nameLength - (int32)oldNameLength; + + ASSERT(lengthDiff <= (int32)FreeSpace()); + + // move names after the changing name + if (index + 1 < entryCount) { + memmove(entryNames + nameOffset + nameLength, + entryNames + nameOffset + oldNameLength, + lastNameEnd - nameOffset - oldNameLength); + } + + // update the name ends + for (int32 i = index; i < entryCount; i++) + fBlock->nameEnds[i] = (int32)fBlock->nameEnds[i] + lengthDiff; + } + + // copy the name + memcpy(entryNames + nameOffset, name, nameLength); +ASSERT(Check()); +} + + +void +DirEntryBlock::RemoveEntry(int32 index) +{ + ASSERT(index >= 0 && index < EntryCount()); + + int32 entryCount = EntryCount(); + if (entryCount == 1) { + // simple case -- removing the last entry + fBlock->entryCount = 0; + return; + } + + uint64* blockIndices = (uint64*)((uint8*)fBlock + fBlockSize) - 1; + char* entryNames = (char*)(fBlock->nameEnds + entryCount); + + uint32 nameOffset = index == 0 ? 0 : fBlock->nameEnds[index - 1]; + uint32 nameEnd = fBlock->nameEnds[index]; + uint32 lastNameEnd = entryCount == 0 + ? 0 : fBlock->nameEnds[entryCount - 1]; + + if (index < entryCount - 1) { + uint32 nameLength = nameEnd - nameOffset; + + // remove the element from the block indices array + memmove(&blockIndices[-entryCount + 2], &blockIndices[-entryCount + 1], + 8 * (entryCount - index - 1)); + + // move and update the nameEnds entries > index + for (int32 i = index + 1; i < entryCount; i++) + fBlock->nameEnds[i - 1] = fBlock->nameEnds[i] - nameLength; + + // move the names < index by 2 bytes + if (index > 0) + memmove(entryNames - 2, entryNames, nameOffset); + + // move the names > index + memmove(entryNames - 2 + nameOffset, entryNames + nameEnd, + lastNameEnd - nameEnd); + } else { + // the nameEnds array shrinks -- move all names + memmove(entryNames - 2, entryNames, nameOffset); + } + + // we have removed the entry + fBlock->entryCount--; +ASSERT(Check()); +} + + +/*! Moves all entries beyond \a splitIndex (inclusively) to the empty block + \a other. +*/ +void +DirEntryBlock::SplitBlock(int32 splitIndex, DirEntryBlock& other) +{ + ASSERT(other.EntryCount() == 0); + ASSERT(splitIndex <= EntryCount()); + + int32 entryCount = EntryCount(); + if (splitIndex == entryCount) + return; + int32 otherEntryCount = entryCount - splitIndex; + + // copy block indices + uint64* blockIndices = (uint64*)((uint8*)fBlock + fBlockSize); + uint64* otherBlockIndices + = (uint64*)((uint8*)other.fBlock + other.fBlockSize); + // note: both point after the last entry, unlike in other methods + memcpy(otherBlockIndices - otherEntryCount, blockIndices - entryCount, + 8 * otherEntryCount); + + // copy the name end offsets + uint32 namesOffset = splitIndex > 0 + ? fBlock->nameEnds[splitIndex - 1] : 0; + for (int32 i = splitIndex; i < entryCount; i++) { + other.fBlock->nameEnds[i - splitIndex] = fBlock->nameEnds[i] + - namesOffset; + } + + // copy the names + char* entryNames = (char*)(fBlock->nameEnds + entryCount); + char* otherEntryNames + = (char*)(other.fBlock->nameEnds + otherEntryCount); + memcpy(otherEntryNames, entryNames + namesOffset, + fBlock->nameEnds[entryCount - 1] - namesOffset); + + // the name ends array shrinks -- move the names + if (splitIndex > 0) { + char* newEntryNames = (char*)(fBlock->nameEnds + splitIndex); + memmove(newEntryNames, entryNames, namesOffset); + } + + // update the entry counts + fBlock->entryCount = splitIndex; + other.fBlock->entryCount = otherEntryCount; +ASSERT(Check()); +ASSERT(other.Check()); +} + + +bool +DirEntryBlock::Check() const +{ + int32 entryCount = EntryCount(); + if (entryCount == 0) + return true; + + // Check size: Both name ends and block index arrays must fit and we need + // at least one byte per name. + size_t size = sizeof(*fBlock) + entryCount * 10; + if (size + entryCount > fBlockSize) { + ERROR("Invalid dir entry block: entry count %d requires minimum size " + "of %" B_PRIuSIZE " + %d bytes, but block size is %" B_PRIuSIZE + "\n", (int)entryCount, size, (int)entryCount, fBlockSize); + return false; + } + + // check the name ends and block indices arrays and the names + const char* entryNames = (char*)(fBlock->nameEnds + entryCount); + const uint64* blockIndices = (uint64*)((uint8*)fBlock + fBlockSize) - 1; + const char* previousName = NULL; + uint16 previousNameLength = 0; + uint16 previousEnd = 0; + + for (int32 i = 0; i < entryCount; i++) { + // check name end + uint16 nameEnd = fBlock->nameEnds[i]; + if (nameEnd <= previousEnd || nameEnd > fBlockSize - size) { + ERROR("Invalid dir entry block: name end offset of entry %" B_PRId32 + ": %u, previous: %u\n", i, nameEnd, previousEnd); + return false; + } + [... truncated: 2278 lines follow ...]