#include "Directory.h"
#include <algorithm>
#include <new>
#include <AutoDeleter.h>
#include "Block.h"
#include "BlockAllocator.h"
#include "DebugSupport.h"
#include "Transaction.h"
#include "Volume.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);
status_t FreeTree(Transaction& transaction);
bool IsEmpty() const;
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;
};
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;
}
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;
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;
}
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;
}
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) {
memmove(&blockIndices[-entryCount], &blockIndices[1 - entryCount],
8 * (entryCount - index));
memmove(entryNames + nameOffset + nameLength + 2,
entryNames + nameOffset, lastNameEnd - nameOffset);
if (index > 0)
memmove(entryNames + 2, entryNames, nameOffset);
for (int32 i = entryCount; i > index; i--)
fBlock->nameEnds[i] = fBlock->nameEnds[i - 1] + nameLength;
} else if (entryCount > 0) {
memmove(entryNames + 2, entryNames, lastNameEnd);
}
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());
if (index + 1 < entryCount) {
memmove(entryNames + nameOffset + nameLength,
entryNames + nameOffset + oldNameLength,
lastNameEnd - nameOffset - oldNameLength);
}
for (int32 i = index; i < entryCount; i++)
fBlock->nameEnds[i] = (int32)fBlock->nameEnds[i] + lengthDiff;
}
memcpy(entryNames + nameOffset, name, nameLength);
ASSERT(Check());
}
void
DirEntryBlock::RemoveEntry(int32 index)
{
ASSERT(index >= 0 && index < EntryCount());
int32 entryCount = EntryCount();
if (entryCount == 1) {
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;
memmove(&blockIndices[-entryCount + 2], &blockIndices[-entryCount + 1],
8 * (entryCount - index - 1));
for (int32 i = index + 1; i < entryCount; i++)
fBlock->nameEnds[i - 1] = fBlock->nameEnds[i] - nameLength;
if (index > 0)
memmove(entryNames - 2, entryNames, nameOffset);
memmove(entryNames - 2 + nameOffset, entryNames + nameEnd,
lastNameEnd - nameEnd);
} else {
memmove(entryNames - 2, entryNames, nameOffset);
}
fBlock->entryCount--;
ASSERT(Check());
}
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;
uint64* blockIndices = (uint64*)((uint8*)fBlock + fBlockSize);
uint64* otherBlockIndices
= (uint64*)((uint8*)other.fBlock + other.fBlockSize);
memcpy(otherBlockIndices - otherEntryCount, blockIndices - entryCount,
8 * otherEntryCount);
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;
}
char* entryNames = (char*)(fBlock->nameEnds + entryCount);
char* otherEntryNames
= (char*)(other.fBlock->nameEnds + otherEntryCount);
memcpy(otherEntryNames, entryNames + namesOffset,
fBlock->nameEnds[entryCount - 1] - namesOffset);
if (splitIndex > 0) {
char* newEntryNames = (char*)(fBlock->nameEnds + splitIndex);
memmove(newEntryNames, entryNames, namesOffset);
}
fBlock->entryCount = splitIndex;
other.fBlock->entryCount = otherEntryCount;
ASSERT(Check());
ASSERT(other.Check());
}
bool
DirEntryBlock::Check() const
{
int32 entryCount = EntryCount();
if (entryCount == 0)
return true;
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;
}
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++) {
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;
}
uint16 nameLength = nameEnd - previousEnd;
if (nameLength > kCheckSumFSNameLength) {
ERROR("Invalid dir entry block: name of entry %" B_PRId32 " too "
"long: %u\n", i, nameLength);
return false;
}
const char* name = entryNames + previousEnd;
if (strnlen(name, nameLength) != nameLength) {
ERROR("Invalid dir entry block: name of entry %" B_PRId32
" contains a null char\n", i);
return false;
}
if (i > 0) {
int cmp = compare_names(previousName, previousNameLength, name,
nameLength);
if (cmp == 0) {
ERROR("Invalid dir entry block: entries %" B_PRId32 "/%"
B_PRId32 " have the same name: \"%.*s\"\n", i - 1, i,
(int)nameLength, name);
return false;
} else if (cmp > 0) {
ERROR("Invalid dir entry block: entries %" B_PRId32 "/%"
B_PRId32 " out of order: \"%.*s\" > \"%.*s\"\n", i - 1, i,
(int)previousNameLength, previousName, (int)nameLength,
name);
return false;
}
}
if (blockIndices[-i] < kCheckSumFSSuperBlockOffset / B_PAGE_SIZE) {
ERROR("Invalid dir entry block: entry %" B_PRId32
" has invalid block index: %" B_PRIu64, i, blockIndices[-i]);
return false;
}
previousName = name;
previousNameLength = nameLength;
previousEnd = nameEnd;
}
return true;
}
DirEntryTree::DirEntryTree(Directory* directory)
:
fDirectory(directory)
{
}
status_t
DirEntryTree::LookupEntry(const char* name, uint64& _blockIndex)
{
FUNCTION("name: \"%s\"\n", name);
status_t error = _InitReadOnly();
if (error != B_OK)
RETURN_ERROR(error);
size_t nameLength = strlen(name);
if (nameLength > kCheckSumFSNameLength)
RETURN_ERROR(B_ENTRY_NOT_FOUND);
uint32 depth = _Depth();
DirEntryBlock entryBlock(fRootEntryBlock, fRootEntryBlockSize);
ASSERT(entryBlock.Check());
Block block;
for (uint32 level = 0; level <= depth; level++) {
if (entryBlock.EntryCount() == 0)
RETURN_ERROR(level == 0 ? B_ENTRY_NOT_FOUND : B_BAD_DATA);
bool exactMatch;
int32 index = entryBlock.FindInsertionIndex(name, nameLength,
exactMatch);
if (!exactMatch) {
if (index == 0) {
RETURN_ERROR(B_ENTRY_NOT_FOUND);
}
index--;
}
PRINT(" level %" B_PRId32 " -> index: %" B_PRId32 " %sexact\n", level,
index, exactMatch ? "" : " not ");
uint64 blockIndex = entryBlock.BlockIndexAt(index);
if (level == depth) {
if (!exactMatch)
RETURN_ERROR(B_ENTRY_NOT_FOUND);
_blockIndex = blockIndex;
return B_OK;
}
if (!block.GetReadable(fDirectory->GetVolume(), blockIndex))
RETURN_ERROR(B_ERROR);
entryBlock.SetTo((checksumfs_dir_entry_block*)block.Data(),
B_PAGE_SIZE);
ASSERT(entryBlock.Check());
}
RETURN_ERROR(B_ENTRY_NOT_FOUND);
}
status_t
DirEntryTree::LookupNextEntry(const char* name, char* foundName,
size_t& _foundNameLength, uint64& _blockIndex)
{
FUNCTION("name: \"%s\"\n", name);
status_t error = _InitReadOnly();
if (error != B_OK)
RETURN_ERROR(error);
size_t nameLength = strlen(name);
if (nameLength > kCheckSumFSNameLength)
RETURN_ERROR(B_ENTRY_NOT_FOUND);
int32 depth = _Depth();
LevelInfo* infos = new(std::nothrow) LevelInfo[
kCheckSumFSMaxDirEntryTreeDepth + 1];
if (infos == NULL)
RETURN_ERROR(B_NO_MEMORY);
ArrayDeleter<LevelInfo> infosDeleter(infos);
infos[0].entryBlock.SetTo(fRootEntryBlock, fRootEntryBlockSize);
ASSERT(infos[0].entryBlock.Check());
for (int32 level = 0; level <= depth; level++) {
LevelInfo& info = infos[level];
if (info.entryBlock.EntryCount() == 0) {
if (level == 0) {
return B_ENTRY_NOT_FOUND;
}
RETURN_ERROR(B_BAD_DATA);
}
info.index = info.entryBlock.FindInsertionIndex(name, nameLength,
info.exactMatch);
PRINT(" level %" B_PRId32 " -> index: %" B_PRId32 " %sexact\n", level,
info.index, info.exactMatch ? "" : " not ");
if (level == depth)
break;
if (!info.exactMatch && info.index > 0)
info.index--;
uint64 nextBlockIndex = info.entryBlock.BlockIndexAt(info.index);
LevelInfo& nextInfo = infos[level + 1];
if (!nextInfo.block.GetReadable(fDirectory->GetVolume(),
nextBlockIndex)) {
RETURN_ERROR(B_ERROR);
}
nextInfo.entryBlock.SetTo(
(checksumfs_dir_entry_block*)nextInfo.block.Data(),
B_PAGE_SIZE);
ASSERT(nextInfo.entryBlock.Check());
}
if (infos[depth].exactMatch)
infos[depth].index++;
if (infos[depth].index >= infos[depth].entryBlock.EntryCount()) {
PRINT(" searching for greater branch\n");
int32 level;
for (level = depth - 1; level >= 0; level--) {
LevelInfo& info = infos[level];
if (++info.index < info.entryBlock.EntryCount()) {
PRINT(" found greater branch: level: %" B_PRId32 " -> index: %"
B_PRId32 "\n", level, info.index);
break;
}
}
if (level < 0)
return B_ENTRY_NOT_FOUND;
for (level++; level <= depth; level++) {
LevelInfo& previousInfo = infos[level - 1];
LevelInfo& info = infos[level];
uint64 nextBlockIndex = previousInfo.entryBlock.BlockIndexAt(
previousInfo.index);
if (!info.block.GetReadable(fDirectory->GetVolume(),
nextBlockIndex)) {
RETURN_ERROR(B_ERROR);
}
info.entryBlock.SetTo(
(checksumfs_dir_entry_block*)info.block.Data(), B_PAGE_SIZE);
ASSERT(info.entryBlock.Check());
info.index = 0;
if (info.entryBlock.EntryCount() == 0)
RETURN_ERROR(B_BAD_DATA);
}
}
LevelInfo& info = infos[depth];
name = info.entryBlock.NameAt(info.index, nameLength);
if (nameLength > kCheckSumFSNameLength
|| strnlen(name, nameLength) != nameLength) {
RETURN_ERROR(B_BAD_DATA);
}
memcpy(foundName, name, nameLength);
foundName[nameLength] = '\0';
_foundNameLength = nameLength;
_blockIndex = info.entryBlock.BlockIndexAt(info.index);
PRINT(" found entry: \"%s\" -> %" B_PRIu64 "\n", foundName, _blockIndex);
return B_OK;
}
status_t
DirEntryTree::InsertEntry(const char* name, uint64 blockIndex,
Transaction& transaction)
{
FUNCTION("name: \"%s\", blockIndex: %" B_PRIu64 "\n", name, blockIndex);
status_t error = _InitWritable(transaction);
if (error != B_OK)
RETURN_ERROR(error);
size_t nameLength = strlen(name);
if (nameLength == 0)
RETURN_ERROR(B_BAD_VALUE);
if (nameLength > kCheckSumFSNameLength)
RETURN_ERROR(B_NAME_TOO_LONG);
int32 depth = _Depth();
LevelInfo* infos = new(std::nothrow) LevelInfo[
kCheckSumFSMaxDirEntryTreeDepth + 1];
if (infos == NULL)
RETURN_ERROR(B_NO_MEMORY);
ArrayDeleter<LevelInfo> infosDeleter(infos);
infos[0].entryBlock.SetTo(fRootEntryBlock, fRootEntryBlockSize);
for (int32 level = 0; level <= depth; level++) {
LevelInfo& info = infos[level];
if (info.entryBlock.EntryCount() == 0) {
if (level == 0) {
PRINT(" directory is empty\n");
info.index = 0;
break;
}
RETURN_ERROR(B_BAD_DATA);
}
info.index = info.entryBlock.FindInsertionIndex(name, nameLength,
info.exactMatch);
PRINT(" level %" B_PRId32 ", block %" B_PRIu64 " -> index: %" B_PRId32
" %sexact\n", level,
level == 0 ? fDirectory->BlockIndex() : info.block.Index(),
info.index, info.exactMatch ? "" : " not ");
if (info.exactMatch)
RETURN_ERROR(B_FILE_EXISTS);
if (level == depth) {
break;
}
info.index--;
uint64 nextBlockIndex = info.entryBlock.BlockIndexAt(info.index);
LevelInfo& nextInfo = infos[level + 1];
if (!nextInfo.block.GetReadable(fDirectory->GetVolume(),
nextBlockIndex)) {
RETURN_ERROR(B_ERROR);
}
nextInfo.entryBlock.SetTo(
(checksumfs_dir_entry_block*)nextInfo.block.Data(),
B_PAGE_SIZE);
ASSERT(nextInfo.entryBlock.Check());
}
return _UpdateOrInsertKey(infos, depth, name, nameLength, blockIndex, true,
transaction);
}
status_t
DirEntryTree::RemoveEntry(const char* name, Transaction& transaction)
{
FUNCTION("name: \"%s\"\n", name);
status_t error = _InitWritable(transaction);
if (error != B_OK)
RETURN_ERROR(error);
size_t nameLength = strlen(name);
if (nameLength == 0)
RETURN_ERROR(B_BAD_VALUE);
if (nameLength > kCheckSumFSNameLength)
RETURN_ERROR(B_ENTRY_NOT_FOUND);
int32 depth = _Depth();
LevelInfo* infos = new(std::nothrow) LevelInfo[
kCheckSumFSMaxDirEntryTreeDepth + 1];
if (infos == NULL)
RETURN_ERROR(B_NO_MEMORY);
ArrayDeleter<LevelInfo> infosDeleter(infos);
infos[0].entryBlock.SetTo(fRootEntryBlock, fRootEntryBlockSize);
for (int32 level = 0; level <= depth; level++) {
LevelInfo& info = infos[level];
if (info.entryBlock.EntryCount() == 0) {
if (level == 0) {
PRINT(" directory is empty\n");
RETURN_ERROR(B_ENTRY_NOT_FOUND);
}
RETURN_ERROR(B_BAD_DATA);
}
info.index = info.entryBlock.FindInsertionIndex(name, nameLength,
info.exactMatch);
PRINT(" level %" B_PRId32 ", block %" B_PRIu64 " -> index: %" B_PRId32
" %sexact\n", level,
level == 0 ? fDirectory->BlockIndex() : info.block.Index(),
info.index, info.exactMatch ? "" : " not ");
if (level == depth) {
if (!info.exactMatch)
RETURN_ERROR(B_ENTRY_NOT_FOUND);
break;
}
if (!info.exactMatch) {
if (info.index == 0) {
RETURN_ERROR(B_ENTRY_NOT_FOUND);
}
info.index--;
}
uint64 nextBlockIndex = info.entryBlock.BlockIndexAt(info.index);
LevelInfo& nextInfo = infos[level + 1];
if (!nextInfo.block.GetReadable(fDirectory->GetVolume(),
nextBlockIndex)) {
RETURN_ERROR(B_ERROR);
}
nextInfo.entryBlock.SetTo(
(checksumfs_dir_entry_block*)nextInfo.block.Data(),
B_PAGE_SIZE);
ASSERT(nextInfo.entryBlock.Check());
}
for (int32 level = depth; level >= 0; level--) {
LevelInfo& info = infos[level];
if (level > 0) {
error = info.block.MakeWritable(transaction);
if (error != B_OK)
RETURN_ERROR(error);
}
PRINT(" level: %" B_PRId32 ", index: %" B_PRId32 ": removing key "
"\"%.*s\" (%" B_PRIuSIZE ")\n", level, info.index, (int)nameLength,
name, nameLength);
if (info.entryBlock.EntryCount() == 1) {
PRINT(" -> block is empty\n");
if (level == 0) {
info.entryBlock.RemoveEntry(info.index);
return B_OK;
}
error = fDirectory->GetVolume()->GetBlockAllocator()->Free(
info.block.Index(), 1, transaction);
if (error != B_OK)
RETURN_ERROR(error);
fDirectory->SetSize(fDirectory->Size() - B_PAGE_SIZE);
continue;
}
info.entryBlock.RemoveEntry(info.index);
if (info.index > 0 || level == 0)
return B_OK;
name = info.entryBlock.NameAt(0, nameLength);
return _UpdateOrInsertKey(infos, level - 1, name, nameLength, 0, false,
transaction);
}
return B_OK;
}
status_t
DirEntryTree::FreeTree(Transaction& transaction)
{
status_t error = _InitReadOnly();
if (error != B_OK)
RETURN_ERROR(error);
int32 depth = _Depth();
if (depth == 0)
return B_OK;
LevelInfo* infos = new(std::nothrow) LevelInfo[
kCheckSumFSMaxDirEntryTreeDepth + 1];
if (infos == NULL)
RETURN_ERROR(B_NO_MEMORY);
ArrayDeleter<LevelInfo> infosDeleter(infos);
infos[0].entryBlock.SetTo(fRootEntryBlock, fRootEntryBlockSize);
infos[0].index = 0;
int32 level = 0;
while (true) {
LevelInfo& info = infos[level];
if (level == depth || info.index >= info.entryBlock.EntryCount()) {
if (level == 0)
break;
error = fDirectory->GetVolume()->GetBlockAllocator()->Free(
info.block.Index(), 1, transaction);
infos[--level].index++;
}
uint64 nextBlockIndex = info.entryBlock.BlockIndexAt(info.index);
LevelInfo& nextInfo = infos[++level];
if (!nextInfo.block.GetReadable(fDirectory->GetVolume(),
nextBlockIndex)) {
RETURN_ERROR(B_ERROR);
}
nextInfo.entryBlock.SetTo(
(checksumfs_dir_entry_block*)nextInfo.block.Data(),
B_PAGE_SIZE);
}
return B_OK;
}
bool
DirEntryTree::IsEmpty() const
{
DirEntryBlock entryBlock(fRootEntryBlock, fRootEntryBlockSize);
return entryBlock.EntryCount() == 0;
}
bool
DirEntryTree::Check()
{
if (_InitReadOnly() != B_OK) {
ERROR("DirEntryTree::Check(): Init failed!\n");
return false;
}
DirEntryBlock entryBlock(fRootEntryBlock, fRootEntryBlockSize);
return _Check(0, fDirectory->BlockIndex(), NULL, 0, entryBlock);
}
status_t
DirEntryTree::_InitReadOnly()
{
if (!fRootBlock.GetReadable(fDirectory->GetVolume(),
fDirectory->BlockIndex())) {
RETURN_ERROR(B_ERROR);
}
return _InitCommon();
}
status_t
DirEntryTree::_InitWritable(Transaction& transaction)
{
if (!fRootBlock.GetWritable(fDirectory->GetVolume(),
fDirectory->BlockIndex(), transaction)) {
RETURN_ERROR(B_ERROR);
}
return _InitCommon();
}
status_t
DirEntryTree::_InitCommon()
{
fTree = (checksumfs_dir_entry_tree*)
((uint8*)fRootBlock.Data() + sizeof(checksumfs_node));
fRootEntryBlock = (checksumfs_dir_entry_block*)(fTree + 1);
fRootEntryBlockSize = B_PAGE_SIZE
- ((addr_t)fRootEntryBlock - (addr_t)fRootBlock.Data());
if (_Depth() > kCheckSumFSMaxDirEntryTreeDepth)
RETURN_ERROR(B_BAD_DATA);
return B_OK;
}
status_t
DirEntryTree::_UpdateOrInsertKey(LevelInfo* infos, int32 level,
const char* name, size_t nameLength, uint64 blockIndex, bool insertKey,
Transaction& transaction)
{
FUNCTION("level: %" B_PRId32 ": %s name: \"%.*s\" (%" B_PRIuSIZE "), "
"blockIndex: %" B_PRIu64 "\n", level, insertKey ? "insert" : "update",
(int)nameLength, name, nameLength, blockIndex);
Block newBlock;
Block tempBlockUpdate;
Block tempBlockUpdateInsert;
Block tempBlockInsert;
int32 depth = _Depth();
status_t error;
bool updateNextKey = !insertKey;
bool insertNextKey = insertKey;
const char* nameToUpdate = name;
size_t nameToUpdateLength = nameLength;
const char* nextNameToInsert = name;
size_t nextNameToInsertLength = nameLength;
uint64 nextBlockIndexToInsert = blockIndex;
for (; level >= 0; level--) {
LevelInfo& info = infos[level];
bool updateThisKey = updateNextKey;
bool insertThisKey = insertNextKey;
if (!updateThisKey && !insertThisKey)
return B_OK;
updateNextKey = false;
insertNextKey = false;
blockIndex = nextBlockIndexToInsert;
name = nextNameToInsert;
nameLength = nextNameToInsertLength;
if (level > 0) {
error = info.block.MakeWritable(transaction);
if (error != B_OK)
RETURN_ERROR(error);
}
if (updateThisKey) {
PRINT(" level: %" B_PRId32 ", index: %" B_PRId32 ": updating key "
"to \"%.*s\" (%" B_PRIuSIZE ")\n", level, info.index,
(int)nameToUpdateLength, nameToUpdate, nameToUpdateLength);
size_t oldNameLength;
info.entryBlock.NameAt(info.index, oldNameLength);
size_t spaceNeeded = oldNameLength < nameToUpdateLength
? nameToUpdateLength - oldNameLength : 0;
if (spaceNeeded <= info.entryBlock.FreeSpace()) {
info.entryBlock.ReplaceEntryName(info.index, nameToUpdate,
nameToUpdateLength);
if (info.index == 0) {
updateNextKey = true;
nameToUpdate = info.entryBlock.NameAt(0,
nameToUpdateLength);
tempBlockUpdate.TransferFrom(info.block);
}
} else if (level == 0) {
error = _InsertEntryIncrementDepth(infos, transaction);
if (error != B_OK)
RETURN_ERROR(error);
level = 2;
updateNextKey = true;
insertNextKey = insertThisKey;
continue;
} else {
int32 splitIndex;
error = _InsertEntrySplitBlock(level, info, spaceNeeded,
transaction, newBlock, splitIndex);
if (error != B_OK)
RETURN_ERROR(error);
nextBlockIndexToInsert = newBlock.Index();
DirEntryBlock newEntryBlock(
(checksumfs_dir_entry_block*)newBlock.Data(),
B_PAGE_SIZE);
ASSERT(newEntryBlock.Check());
if (info.index < splitIndex) {
ASSERT(info.entryBlock.FreeSpace() >= spaceNeeded);
info.entryBlock.ReplaceEntryName(info.index,
nameToUpdate, nameToUpdateLength);
if (info.index == 0) {
updateNextKey = true;
nameToUpdate = info.entryBlock.NameAt(0,
nameToUpdateLength);
tempBlockUpdate.TransferFrom(info.block);
}
} else {
ASSERT(newEntryBlock.FreeSpace() >= spaceNeeded);
info.block.TransferFrom(newBlock);
info.entryBlock.SetTo(
(checksumfs_dir_entry_block*)info.block.Data(),
B_PAGE_SIZE);
ASSERT(info.entryBlock.Check());
info.index -= splitIndex;
info.entryBlock.ReplaceEntryName(info.index, nameToUpdate,
nameToUpdateLength);
}
insertNextKey = true;
nextNameToInsert = newEntryBlock.NameAt(0,
nextNameToInsertLength);
tempBlockUpdateInsert.TransferFrom(newBlock);
}
}
if (insertThisKey) {
if (level < depth)
info.index++;
PRINT(" level: %" B_PRId32 ", index: %" B_PRId32 ": inserting key "
"\"%.*s\" (%" B_PRIuSIZE "), blockIndex: %" B_PRIu64 "\n",
level, info.index, (int)nameLength, name, nameLength,
blockIndex);
if (info.entryBlock.FreeSpace() >= nameLength + 10) {
info.entryBlock.InsertEntry(info.index, name,
nameLength, blockIndex);
if (info.index == 0) {
updateNextKey = true;
nameToUpdate = info.entryBlock.NameAt(0,
nameToUpdateLength);
tempBlockUpdate.TransferFrom(info.block);
}
continue;
}
ASSERT(!insertNextKey);
if (level == 0) {
error = _InsertEntryIncrementDepth(infos, transaction);
if (error != B_OK)
RETURN_ERROR(error);
level = 2;
updateNextKey = false;
insertNextKey = true;
continue;
}
int32 splitIndex;
error = _InsertEntrySplitBlock(level, info, nameLength + 10,
transaction, newBlock, splitIndex);
if (error != B_OK)
RETURN_ERROR(error);
DirEntryBlock newEntryBlock(
(checksumfs_dir_entry_block*)newBlock.Data(),
B_PAGE_SIZE);
ASSERT(newEntryBlock.Check());
if (info.index < splitIndex) {
ASSERT(info.entryBlock.FreeSpace() >= nameLength + 10);
info.entryBlock.InsertEntry(info.index, name,
nameLength, blockIndex);
if (info.index == 0) {
updateNextKey = true;
nameToUpdate = info.entryBlock.NameAt(0,
nameToUpdateLength);
tempBlockUpdate.TransferFrom(info.block);
}
} else {
ASSERT(newEntryBlock.FreeSpace() >= nameLength + 10);
info.index -= splitIndex;
newEntryBlock.InsertEntry(info.index, name, nameLength,
blockIndex);
}
insertNextKey = true;
nextNameToInsert = newEntryBlock.NameAt(0, nextNameToInsertLength);
nextBlockIndexToInsert = newBlock.Index();
tempBlockInsert.TransferFrom(newBlock);
}
}
return B_OK;
}
status_t
DirEntryTree::_InsertEntryIncrementDepth(LevelInfo* infos,
Transaction& transaction)
{
FUNCTION("depth: %u -> %u\n", _Depth(), _Depth() + 1);
if (_Depth() >= kCheckSumFSMaxDirEntryTreeDepth)
RETURN_ERROR(B_DEVICE_FULL);
AllocatedBlock allocatedBlock(
fDirectory->GetVolume()->GetBlockAllocator(), transaction);
status_t error = allocatedBlock.Allocate(fDirectory->BlockIndex());
if (error != B_OK)
RETURN_ERROR(error);
fDirectory->SetSize(fDirectory->Size() + B_PAGE_SIZE);
LevelInfo& newInfo = infos[1];
if (!newInfo.block.GetZero(fDirectory->GetVolume(),
allocatedBlock.Index(), transaction)) {
RETURN_ERROR(B_ERROR);
}
allocatedBlock.Detach();
newInfo.entryBlock.SetTo(
(checksumfs_dir_entry_block*)newInfo.block.Data(), B_PAGE_SIZE);
ASSERT(newInfo.entryBlock.Check());
LevelInfo& rootInfo = infos[0];
rootInfo.entryBlock.SplitBlock(0, newInfo.entryBlock);
size_t nameLength;
const char* name = newInfo.entryBlock.NameAt(0, nameLength);
rootInfo.entryBlock.InsertEntry(0, name, nameLength,
newInfo.block.Index());
PRINT(" -> new block: %" B_PRIu64 "\n", newInfo.block.Index());
newInfo.index = rootInfo.index;
rootInfo.index = 0;
fTree->depth++;
return B_OK;
}
status_t
DirEntryTree::_InsertEntrySplitBlock(int32 level, LevelInfo& info,
size_t needed, Transaction& transaction, Block& newBlock,
int32& _splitIndex)
{
int32 splitIndex = info.entryBlock.FindSplitIndex(info.index,
needed);
FUNCTION("level: %" B_PRId32 ", size needed: %" B_PRIuSIZE ", split index: "
"%" B_PRId32 "/%" B_PRId32 "\n", level, needed, splitIndex,
info.entryBlock.EntryCount());
AllocatedBlock allocatedBlock(
fDirectory->GetVolume()->GetBlockAllocator(), transaction);
status_t error = allocatedBlock.Allocate(fDirectory->BlockIndex());
if (error != B_OK)
RETURN_ERROR(error);
fDirectory->SetSize(fDirectory->Size() + B_PAGE_SIZE);
if (!newBlock.GetZero(fDirectory->GetVolume(), allocatedBlock.Index(),
transaction)) {
RETURN_ERROR(B_ERROR);
}
allocatedBlock.Detach();
DirEntryBlock newEntryBlock(
(checksumfs_dir_entry_block*)newBlock.Data(), B_PAGE_SIZE);
ASSERT(newEntryBlock.Check());
info.entryBlock.SplitBlock(splitIndex, newEntryBlock);
PRINT(" -> new block: %" B_PRIu64 "\n", newBlock.Index());
_splitIndex = splitIndex;
return B_OK;
}
bool
DirEntryTree::_Check(int32 level, uint64 blockIndex, const char* key,
size_t keyLength, DirEntryBlock& entryBlock)
{
if (!entryBlock.Check()) {
ERROR("DirEntryTree::Check(): level %" B_PRIu32 ": block %"
B_PRIu64 " not valid!\n", level, blockIndex);
return false;
}
uint32 entryCount = entryBlock.EntryCount();
if (entryCount == 0) {
if (level == 0)
return true;
ERROR("DirEntryTree::Check(): level %" B_PRIu32 ": block %"
B_PRIu64 " empty!\n", level, blockIndex);
return false;
}
if (level > 0) {
size_t nameLength;
const char* name = entryBlock.NameAt(0, nameLength);
if (nameLength != keyLength || strncmp(name, key, keyLength) != 0) {
ERROR("DirEntryTree::Check(): level %" B_PRIu32 ": block %"
B_PRIu64 " key mismatch: is \"%.*s\", should be \"%.*s\"\n",
level, blockIndex, (int)keyLength, key, (int)nameLength, name);
return false;
}
}
if (level == _Depth())
return true;
for (uint32 i = 0; i < entryCount; i++) {
size_t nameLength;
const char* name = entryBlock.NameAt(i, nameLength);
uint64 childBlockIndex = entryBlock.BlockIndexAt(i);
Block childBlock;
if (!childBlock.GetReadable(fDirectory->GetVolume(), childBlockIndex)) {
ERROR("DirEntryTree::Check(): level %" B_PRIu32 ": block %"
B_PRIu64 " failed to get child block %" B_PRIu64 " (entry %"
B_PRIu32 ")\n", level, blockIndex, childBlockIndex, i);
}
DirEntryBlock childEntryBlock(
(checksumfs_dir_entry_block*)childBlock.Data(), B_PAGE_SIZE);
if (!_Check(level + 1, childBlockIndex, name, nameLength,
childEntryBlock)) {
return false;
}
}
return true;
}
Directory::Directory(Volume* volume, uint64 blockIndex,
const checksumfs_node& nodeData)
:
Node(volume, blockIndex, nodeData)
{
}
Directory::Directory(Volume* volume, mode_t mode)
:
Node(volume, mode)
{
}
Directory::~Directory()
{
}
void
Directory::DeletingNode()
{
Node::DeletingNode();
char* name = (char*)malloc(kCheckSumFSNameLength + 1);
if (name != NULL) {
name[0] = '\0';
DirEntryTree entryTree(this);
size_t nameLength;
uint64 blockIndex;
while (entryTree.LookupNextEntry(name, name, nameLength,
blockIndex) == B_OK) {
Node* node;
if (GetVolume()->GetNode(blockIndex, node) == B_OK) {
Transaction transaction(GetVolume());
if (transaction.StartAndAddNode(node) == B_OK) {
node->SetHardLinks(node->HardLinks() - 1);
if (node->HardLinks() == 0)
GetVolume()->RemoveNode(node);
if (transaction.Commit() != B_OK) {
ERROR("Failed to commit transaction for dereferencing "
"entry node of deleted directory at %" B_PRIu64
"\n", BlockIndex());
}
} else {
ERROR("Failed to start transaction for dereferencing "
"entry node of deleted directory at %" B_PRIu64 "\n",
BlockIndex());
}
GetVolume()->PutNode(node);
} else {
ERROR("Failed to get node %" B_PRIu64 " referenced by deleted "
"directory at %" B_PRIu64 "\n", blockIndex, BlockIndex());
}
}
free(name);
}
Transaction transaction(GetVolume());
if (transaction.Start() != B_OK) {
ERROR("Failed to start transaction for freeing entry tree of deleted "
"directory at %" B_PRIu64 "\n", BlockIndex());
return;
}
DirEntryTree entryTree(this);
if (entryTree.FreeTree(transaction) != B_OK) {
ERROR("Failed to freeing entry tree of deleted directory at %" B_PRIu64
"\n", BlockIndex());
return;
}
if (transaction.Commit() != B_OK) {
ERROR("Failed to commit transaction for freeing entry tree of deleted "
"directory at %" B_PRIu64 "\n", BlockIndex());
}
}
status_t
Directory::LookupEntry(const char* name, uint64& _blockIndex)
{
DirEntryTree entryTree(this);
return entryTree.LookupEntry(name, _blockIndex);
}
status_t
Directory::LookupNextEntry(const char* name, char* foundName,
size_t& _foundNameLength, uint64& _blockIndex)
{
DirEntryTree entryTree(this);
return entryTree.LookupNextEntry(name, foundName, _foundNameLength,
_blockIndex);
}
status_t
Directory::InsertEntry(const char* name, uint64 blockIndex,
Transaction& transaction)
{
DirEntryTree entryTree(this);
status_t error = entryTree.InsertEntry(name, blockIndex, transaction);
if (error == B_OK)
ASSERT(entryTree.Check());
return error;
}
status_t
Directory::RemoveEntry(const char* name, Transaction& transaction,
bool* _lastEntryRemoved)
{
DirEntryTree entryTree(this);
status_t error = entryTree.RemoveEntry(name, transaction);
if (error != B_OK)
return error;
ASSERT(entryTree.Check());
if (_lastEntryRemoved != NULL)
*_lastEntryRemoved = entryTree.IsEmpty();
return B_OK;
}