root/src/add-ons/kernel/file_systems/ext2/BlockAllocator.cpp
/*
 * Copyright 2001-2011, Haiku Inc. All rights reserved.
 * This file may be used under the terms of the MIT License.
 *
 * Authors:
 *              Janito V. Ferreira Filho
 *              Jérôme Duval
 */


#include "BlockAllocator.h"

#include <util/AutoLock.h>

#include "BitmapBlock.h"
#include "Inode.h"


#undef ASSERT
//#define TRACE_EXT2
#ifdef TRACE_EXT2
#       define TRACE(x...) dprintf("\33[34mext2:\33[0m " x)
#       define ASSERT(x) { if (!(x)) kernel_debugger("ext2: assert failed: " #x "\n"); }
#else
#       define TRACE(x...) ;
#       define ASSERT(x) ;
#endif
#define ERROR(x...) dprintf("\33[34mext2:\33[0m " x)


class AllocationBlockGroup : public TransactionListener {
public:
                                                AllocationBlockGroup();
                                                ~AllocationBlockGroup();

                        status_t        Initialize(Volume* volume, uint32 blockGroup,
                                                        uint32 numBits);

                        bool            IsFull() const;

                        status_t        Allocate(Transaction& transaction, fsblock_t start,
                                                        uint32 length);
                        status_t        Free(Transaction& transaction, uint32 start,
                                                        uint32 length);
                        status_t        FreeAll(Transaction& transaction);
                        status_t        Check(uint32 start, uint32 length);

                        uint32          NumBits() const;
                        uint32          FreeBits() const;
                        fsblock_t       Start() const;

                        fsblock_t       LargestStart() const;
                        uint32          LargestLength() const;

                        // TransactionListener implementation
                        void            TransactionDone(bool success);
                        void            RemovedFromTransaction();

private:
                        status_t        _ScanFreeRanges(bool verify = false);
                        void            _AddFreeRange(uint32 start, uint32 length);
                        void            _LockInTransaction(Transaction& transaction);
                        status_t        _InitGroup(Transaction& transaction);
                        bool            _IsSparse();
                        uint32          _FirstFreeBlock();
                        void            _SetBlockBitmapChecksum(BitmapBlock& block);
                        bool            _VerifyBlockBitmapChecksum(BitmapBlock& block);

                        Volume*         fVolume;
                        uint32          fBlockGroup;
                        ext2_block_group* fGroupDescriptor;

                        mutex           fLock;
                        mutex           fTransactionLock;
                        int32           fCurrentTransaction;

                        fsblock_t       fStart;
                        uint32          fNumBits;
                        fsblock_t       fBitmapBlock;

                        uint32          fFreeBits;
                        uint32          fFirstFree;
                        uint32          fLargestStart;
                        uint32          fLargestLength;
                        
                        uint32          fPreviousFreeBits;
                        uint32          fPreviousFirstFree;
                        uint32          fPreviousLargestStart;
                        uint32          fPreviousLargestLength;
};


AllocationBlockGroup::AllocationBlockGroup()
        :
        fVolume(NULL),
        fBlockGroup(0),
        fGroupDescriptor(NULL),
        fCurrentTransaction(-1),
        fStart(0),
        fNumBits(0),
        fBitmapBlock(0),
        fFreeBits(0),
        fFirstFree(0),
        fLargestStart(0),
        fLargestLength(0),
        fPreviousFreeBits(0),
        fPreviousFirstFree(0),
        fPreviousLargestStart(0),
        fPreviousLargestLength(0)
{
        mutex_init(&fLock, "ext2 allocation block group");
        mutex_init(&fTransactionLock, "ext2 allocation block group transaction");
}


AllocationBlockGroup::~AllocationBlockGroup()
{
        mutex_destroy(&fLock);
        mutex_destroy(&fTransactionLock);
}


status_t
AllocationBlockGroup::Initialize(Volume* volume, uint32 blockGroup,
        uint32 numBits)
{
        fVolume = volume;
        fBlockGroup = blockGroup;
        fNumBits = numBits;
        fStart = blockGroup * numBits + fVolume->FirstDataBlock();

        status_t status = fVolume->GetBlockGroup(blockGroup, &fGroupDescriptor);
        if (status != B_OK)
                return status;

        fBitmapBlock = fGroupDescriptor->BlockBitmap(fVolume->Has64bitFeature());
        if (fBitmapBlock == 0) {
                ERROR("AllocationBlockGroup(%" B_PRIu32 ")::Initialize(): "
                        "blockBitmap is zero\n", fBlockGroup);
                return B_BAD_VALUE;
        }

        if (fGroupDescriptor->Flags() & EXT2_BLOCK_GROUP_BLOCK_UNINIT) {
                fFreeBits = fGroupDescriptor->FreeBlocks(fVolume->Has64bitFeature());
                fLargestLength = fFreeBits;
                fLargestStart = _FirstFreeBlock();
                TRACE("Group %" B_PRIu32 " is uninit\n", fBlockGroup);
                return B_OK;
        }
        
        status = _ScanFreeRanges(true);
        if (status != B_OK)
                return status;

        if (fGroupDescriptor->FreeBlocks(fVolume->Has64bitFeature())
                != fFreeBits) {
                ERROR("AllocationBlockGroup(%" B_PRIu32 ",%" B_PRIu64 ")::Initialize()"
                        ": Mismatch between counted free blocks (%" B_PRIu32 "/%" B_PRIu32
                        ") and what is set on the group descriptor (%" B_PRIu32 ")\n",
                        fBlockGroup, fBitmapBlock, fFreeBits, fNumBits,
                        fGroupDescriptor->FreeBlocks(fVolume->Has64bitFeature()));
                return B_BAD_DATA;
        }

        fPreviousFreeBits = fFreeBits;
        fPreviousFirstFree = fFirstFree;
        fPreviousLargestStart = fLargestStart;
        fPreviousLargestLength = fLargestLength;
        
        return status;
}


status_t
AllocationBlockGroup::_ScanFreeRanges(bool verify)
{
        TRACE("AllocationBlockGroup::_ScanFreeRanges() for group %" B_PRIu32 "\n",
                fBlockGroup);
        BitmapBlock block(fVolume, fNumBits);

        if (!block.SetTo(fBitmapBlock))
                return B_ERROR;

        if (verify && !_VerifyBlockBitmapChecksum(block)) {
                ERROR("AllocationBlockGroup(%" B_PRIu32 ")::_ScanFreeRanges(): "
                        "blockGroup verification failed\n", fBlockGroup);
                return B_BAD_DATA;
        }

        fFreeBits = 0;
        uint32 start = 0;
        uint32 end = 0;

        while (end < fNumBits) {
                block.FindNextUnmarked(start);
                ASSERT(block.CheckMarked(end, start - end));
                end = start;

                if (start != block.NumBits()) {
                        block.FindNextMarked(end);
                        _AddFreeRange(start, end - start);
                        ASSERT(block.CheckUnmarked(fLargestStart, fLargestLength));
                        start = end;
                }
        }

        return B_OK;
}


bool
AllocationBlockGroup::IsFull() const
{
        return fFreeBits == 0;
}


status_t
AllocationBlockGroup::Allocate(Transaction& transaction, fsblock_t _start,
        uint32 length)
{
        uint32 start = _start - fStart;
        TRACE("AllocationBlockGroup::Allocate(%" B_PRIu32 ",%" B_PRIu32 ")\n",
                start, length);
        if (length == 0)
                return B_OK;

        uint32 end = start + length;
        if (end > fNumBits)
                return B_BAD_VALUE;

        _LockInTransaction(transaction);
        _InitGroup(transaction);

        BitmapBlock block(fVolume, fNumBits);

        if (!block.SetToWritable(transaction, fBitmapBlock))
                return B_ERROR;

        TRACE("AllocationBlockGroup::Allocate(): Largest range in %" B_PRIu32 "-%"
                B_PRIu32 "\n", fLargestStart, fLargestStart + fLargestLength);
        ASSERT(block.CheckUnmarked(fLargestStart, fLargestLength));
        
        if (!block.Mark(start, length)) {
                ERROR("Failed to allocate blocks from %" B_PRIu32 " to %" B_PRIu32
                        ". Some were already allocated.\n", start, start + length);
                return B_ERROR;
        }

        fFreeBits -= length;
        fGroupDescriptor->SetFreeBlocks(fFreeBits, fVolume->Has64bitFeature());
        _SetBlockBitmapChecksum(block);
        fVolume->WriteBlockGroup(transaction, fBlockGroup);

        if (start == fLargestStart) {
                if (fFirstFree == fLargestStart)
                        fFirstFree += length;

                fLargestStart += length;
                fLargestLength -= length;
        } else if (start + length == fLargestStart + fLargestLength) {
                fLargestLength -= length;
        } else if (start < fLargestStart + fLargestLength
                        && start > fLargestStart) {
                uint32 firstLength = start - fLargestStart;
                uint32 secondLength = fLargestStart + fLargestLength
                        - (start + length);

                if (firstLength >= secondLength) {
                        fLargestLength = firstLength;
                } else {
                        fLargestLength = secondLength;
                        fLargestStart = start + length;
                }
        } else {
                // No need to revalidate the largest free range
                return B_OK;
        }

        TRACE("AllocationBlockGroup::Allocate(): Largest range in %" B_PRIu32 "-%"
                B_PRIu32 "\n", fLargestStart, fLargestStart + fLargestLength);
        ASSERT(block.CheckUnmarked(fLargestStart, fLargestLength));

        if (fLargestLength < fNumBits / 2)
                block.FindLargestUnmarkedRange(fLargestStart, fLargestLength);
        ASSERT(block.CheckUnmarked(fLargestStart, fLargestLength));

        return B_OK;
}


status_t
AllocationBlockGroup::Free(Transaction& transaction, uint32 start,
        uint32 length)
{
        TRACE("AllocationBlockGroup::Free(): start: %" B_PRIu32 ", length %"
                B_PRIu32 "\n", start, length);

        if (length == 0)
                return B_OK;

        uint32 end = start + length;
        if (end > fNumBits)
                return B_BAD_VALUE;

        _LockInTransaction(transaction);
        if (fGroupDescriptor->Flags() & EXT2_BLOCK_GROUP_BLOCK_UNINIT)
                panic("AllocationBlockGroup::Free() can't free blocks if uninit\n");

        BitmapBlock block(fVolume, fNumBits);

        if (!block.SetToWritable(transaction, fBitmapBlock))
                return B_ERROR;

        TRACE("AllocationBlockGroup::Free(): Largest range in %" B_PRIu32 "-%"
                B_PRIu32 "\n", fLargestStart, fLargestStart + fLargestLength);
        ASSERT(block.CheckUnmarked(fLargestStart, fLargestLength));

        if (!block.Unmark(start, length)) {
                ERROR("Failed to free blocks from %" B_PRIu32 " to %" B_PRIu32
                        ". Some were already freed.\n", start, start + length);
                return B_ERROR;
        }

        TRACE("AllocationGroup::Free(): Unmarked bits in bitmap\n");

        if (fFirstFree > start)
                fFirstFree = start;

        if (start + length == fLargestStart) {
                fLargestStart = start;
                fLargestLength += length;
        } else if (start == fLargestStart + fLargestLength) {
                fLargestLength += length;
        } else if (fLargestLength <= fNumBits / 2) {
                // May have merged with some free blocks, becoming the largest
                uint32 newEnd = start + length;
                block.FindNextMarked(newEnd);

                uint32 newStart = start;
                block.FindPreviousMarked(newStart);
                newStart++;

                if (newEnd - newStart > fLargestLength) {
                        fLargestLength = newEnd - newStart;
                        fLargestStart = newStart;
                }
        }

        TRACE("AllocationBlockGroup::Free(): Largest range in %" B_PRIu32 "-%"
                B_PRIu32 "\n", fLargestStart, fLargestStart + fLargestLength);
        ASSERT(block.CheckUnmarked(fLargestStart, fLargestLength));

        fFreeBits += length;
        fGroupDescriptor->SetFreeBlocks(fFreeBits, fVolume->Has64bitFeature());
        _SetBlockBitmapChecksum(block);
        fVolume->WriteBlockGroup(transaction, fBlockGroup);

        return B_OK;
}


status_t
AllocationBlockGroup::FreeAll(Transaction& transaction)
{
        return Free(transaction, 0, fNumBits);
}


uint32
AllocationBlockGroup::NumBits() const
{
        return fNumBits;
}


uint32
AllocationBlockGroup::FreeBits() const
{
        return fFreeBits;
}


fsblock_t
AllocationBlockGroup::Start() const
{
        return fStart;
}


fsblock_t
AllocationBlockGroup::LargestStart() const
{
        return fStart + fLargestStart;
}


uint32
AllocationBlockGroup::LargestLength() const
{
        return fLargestLength;
}


void
AllocationBlockGroup::_AddFreeRange(uint32 start, uint32 length)
{
        if (IsFull()) {
                fFirstFree = start;
                fLargestStart = start;
                fLargestLength = length;
        } else if (length > fLargestLength) {
                fLargestStart = start;
                fLargestLength = length;
        }

        fFreeBits += length;
}


void
AllocationBlockGroup::_LockInTransaction(Transaction& transaction)
{
        mutex_lock(&fLock);

        if (transaction.ID() != fCurrentTransaction) {
                mutex_unlock(&fLock);

                mutex_lock(&fTransactionLock);
                mutex_lock(&fLock);

                fCurrentTransaction = transaction.ID();
                transaction.AddListener(this);
        }

        mutex_unlock(&fLock);
}


status_t
AllocationBlockGroup::_InitGroup(Transaction& transaction)
{
        TRACE("AllocationBlockGroup::_InitGroup()\n");
        uint16 flags = fGroupDescriptor->Flags();
        if ((flags & EXT2_BLOCK_GROUP_BLOCK_UNINIT) == 0)
                return B_OK;

        TRACE("AllocationBlockGroup::_InitGroup() initing\n");

        BitmapBlock blockBitmap(fVolume, fNumBits);
        if (!blockBitmap.SetToWritable(transaction, fBitmapBlock))
                return B_ERROR;

        blockBitmap.Mark(0, _FirstFreeBlock(), true);
        blockBitmap.Unmark(0, fNumBits, true);
        
        fGroupDescriptor->SetFlags(flags & ~EXT2_BLOCK_GROUP_BLOCK_UNINIT);
        _SetBlockBitmapChecksum(blockBitmap);
        fVolume->WriteBlockGroup(transaction, fBlockGroup);

        status_t status = _ScanFreeRanges();
        if (status != B_OK)
                return status;

        if (fGroupDescriptor->FreeBlocks(fVolume->Has64bitFeature())
                != fFreeBits) {
                ERROR("AllocationBlockGroup(%" B_PRIu32 ",%" B_PRIu64 ")::_InitGroup()"
                        ": Mismatch between counted free blocks (%" B_PRIu32 "/%" B_PRIu32
                        ") and what is set on the group descriptor (%" B_PRIu32 ")\n",
                        fBlockGroup, fBitmapBlock, fFreeBits, fNumBits,
                        fGroupDescriptor->FreeBlocks(fVolume->Has64bitFeature()));
                return B_BAD_DATA;
        }

        TRACE("AllocationBlockGroup::_InitGroup() init OK\n");

        return B_OK;
}


bool
AllocationBlockGroup::_IsSparse()
{
        if (fBlockGroup <= 1)
                return true;
        if (fBlockGroup & 0x1)
                return false;

        uint32 i = fBlockGroup;
        while (i % 7 == 0)
                i /= 7;
        if (i == 1)
                return true;

        i = fBlockGroup;
        while (i % 5 == 0)
                i /= 5;
        if (i == 1)
                return true;

        i = fBlockGroup;
        while (i % 3 == 0)
                i /= 3;
        if (i == 1)
                return true;

        return false;
}


uint32
AllocationBlockGroup::_FirstFreeBlock()
{
        uint32 first = 1;
        if (_IsSparse())
                first = 0;
        else if (!fVolume->HasMetaGroupFeature()) {
                first += fVolume->SuperBlock().ReservedGDTBlocks();
                first += fVolume->NumGroups();
        }
        return first;
}


void
AllocationBlockGroup::_SetBlockBitmapChecksum(BitmapBlock& block)
{
        if (fVolume->HasMetaGroupChecksumFeature()) {
                uint32 checksum = block.Checksum(fVolume->BlocksPerGroup());
                fGroupDescriptor->block_bitmap_csum = checksum & 0xffff;
                if (fVolume->GroupDescriptorSize() >= offsetof(ext2_block_group,
                        inode_bitmap_high)) {
                        fGroupDescriptor->block_bitmap_csum_high = checksum >> 16;
                }
        }
}


bool
AllocationBlockGroup::_VerifyBlockBitmapChecksum(BitmapBlock& block) {
        if (fVolume->HasMetaGroupChecksumFeature()) {
                uint32 checksum = block.Checksum(fVolume->BlocksPerGroup());
                uint32 provided = fGroupDescriptor->block_bitmap_csum;
                if (fVolume->GroupDescriptorSize() >= offsetof(ext2_block_group,
                        inode_bitmap_high)) {
                        provided |= (fGroupDescriptor->block_bitmap_csum_high << 16);
                } else
                        checksum &= 0xffff;
                return provided == checksum;
        }
        return true;
}


void
AllocationBlockGroup::TransactionDone(bool success)
{
        if (success) {
                TRACE("AllocationBlockGroup::TransactionDone(): The transaction "
                        "succeeded, discarding previous state\n");
                fPreviousFreeBits = fFreeBits;
                fPreviousFirstFree = fFirstFree;
                fPreviousLargestStart = fLargestStart;
                fPreviousLargestLength = fLargestLength;
        } else {
                TRACE("AllocationBlockGroup::TransactionDone(): The transaction "
                        "failed, restoring to previous state\n");
                fFreeBits = fPreviousFreeBits;
                fFirstFree = fPreviousFirstFree;
                fLargestStart = fPreviousLargestStart;
                fLargestLength = fPreviousLargestLength;
        }
}


void
AllocationBlockGroup::RemovedFromTransaction()
{
        mutex_unlock(&fTransactionLock);
        fCurrentTransaction = -1;
}


BlockAllocator::BlockAllocator(Volume* volume)
        :
        fVolume(volume),
        fGroups(NULL),
        fBlocksPerGroup(0),
        fNumBlocks(0),
        fNumGroups(0),
        fFirstBlock(0)
{
        mutex_init(&fLock, "ext2 block allocator");
}


BlockAllocator::~BlockAllocator()
{
        mutex_destroy(&fLock);

        if (fGroups != NULL)
                delete [] fGroups;
}


status_t
BlockAllocator::Initialize()
{
        fBlocksPerGroup = fVolume->BlocksPerGroup();
        fNumGroups = fVolume->NumGroups();
        fFirstBlock = fVolume->FirstDataBlock();
        fNumBlocks = fVolume->NumBlocks();
        
        TRACE("BlockAllocator::Initialize(): blocks per group: %" B_PRIu32
                ", block groups: %" B_PRIu32 ", first block: %" B_PRIu64
                ", num blocks: %" B_PRIu64 "\n", fBlocksPerGroup, fNumGroups,
                fFirstBlock, fNumBlocks);

        fGroups = new(std::nothrow) AllocationBlockGroup[fNumGroups];
        if (fGroups == NULL)
                return B_NO_MEMORY;

        TRACE("BlockAllocator::Initialize(): allocated allocation block groups\n");

        mutex_lock(&fLock);
                // Released by _Initialize

#if 0
        thread_id id = spawn_kernel_thread(
                (thread_func)BlockAllocator::_Initialize, "ext2 block allocator",
                B_LOW_PRIORITY, this);
        if (id < B_OK)
                return _Initialize(this);

        mutex_transfer_lock(&fLock, id);

        return resume_thread(id);
#else
        return _Initialize(this);
#endif
}


status_t
BlockAllocator::AllocateBlocks(Transaction& transaction, uint32 minimum,
        uint32 maximum, uint32& blockGroup, fsblock_t& start, uint32& length)
{
        TRACE("BlockAllocator::AllocateBlocks()\n");
        MutexLocker lock(fLock);
        TRACE("BlockAllocator::AllocateBlocks(): Acquired lock\n");

        TRACE("BlockAllocator::AllocateBlocks(): transaction: %" B_PRId32 ", min: "
                "%" B_PRIu32 ", max: %" B_PRIu32 ", block group: %" B_PRIu32 ", start:"
                " %" B_PRIu64 ", num groups: %" B_PRIu32 "\n", transaction.ID(),
                minimum, maximum, blockGroup, start, fNumGroups);

        fsblock_t bestStart = 0;
        uint32 bestLength = 0;
        uint32 bestGroup = 0;

        uint32 groupNum = blockGroup;

        AllocationBlockGroup* last = &fGroups[fNumGroups];
        AllocationBlockGroup* group = &fGroups[blockGroup];

        for (int32 iterations = 0; iterations < 2; iterations++) {
                for (; group < last; ++group, ++groupNum) {
                        TRACE("BlockAllocator::AllocateBlocks(): Group %" B_PRIu32
                                " has largest length of %" B_PRIu32 "\n", groupNum,
                                group->LargestLength());

                        if (group->LargestLength() > bestLength) {
                                if (start <= group->LargestStart()) {
                                        bestStart = group->LargestStart();
                                        bestLength = group->LargestLength();
                                        bestGroup = groupNum;

                                        TRACE("BlockAllocator::AllocateBlocks(): Found a better "
                                                "range: block group: %" B_PRIu32 ", %" B_PRIu64 "-%"
                                                B_PRIu64 "\n", groupNum, bestStart,
                                                bestStart + bestLength);

                                        if (bestLength >= maximum)
                                                break;
                                }
                        }

                        start = 0;
                }

                if (bestLength >= maximum)
                        break;

                groupNum = 0;

                group = &fGroups[0];
                last = &fGroups[blockGroup + 1];
        }

        if (bestLength < minimum) {
                TRACE("BlockAllocator::AllocateBlocks(): best range (length %" B_PRIu32
                        ") doesn't have minimum length of %" B_PRIu32 "\n", bestLength,
                        minimum);
                return B_DEVICE_FULL;
        }

        if (bestLength > maximum)
                bestLength = maximum;

        TRACE("BlockAllocator::AllocateBlocks(): Selected range: block group %"
                B_PRIu32 ", %" B_PRIu64 "-%" B_PRIu64 "\n", bestGroup, bestStart,
                bestStart + bestLength);

        status_t status = fGroups[bestGroup].Allocate(transaction, bestStart,
                bestLength);
        if (status != B_OK) {
                TRACE("BlockAllocator::AllocateBlocks(): Failed to allocate %" B_PRIu32
                " blocks inside block group %" B_PRIu32 ".\n", bestLength, bestGroup);
                return status;
        }

        start = bestStart;
        length = bestLength;
        blockGroup = bestGroup;

        return B_OK;
}


status_t
BlockAllocator::Allocate(Transaction& transaction, Inode* inode,
        off_t numBlocks, uint32 minimum, fsblock_t& start, uint32& allocated)
{
        if (numBlocks <= 0)
                return B_ERROR;
        if (start > fNumBlocks)
                return B_BAD_VALUE;

        uint32 group = inode->ID() / fVolume->InodesPerGroup();

        if (inode->Size() > 0) {
                // Try to allocate near it's last blocks
                ext2_data_stream* dataStream = &inode->Node().stream;
                uint32 numBlocks = inode->Size() / fVolume->BlockSize() + 1;
                uint32 lastBlock = 0;

                // DANGER! What happens with sparse files?
                if (numBlocks < EXT2_DIRECT_BLOCKS) {
                        // Only direct blocks
                        lastBlock = dataStream->direct[numBlocks];
                } else {
                        numBlocks -= EXT2_DIRECT_BLOCKS - 1;
                        uint32 numIndexes = fVolume->BlockSize() / 4;
                                // block size / sizeof(int32)
                        uint32 numIndexes2 = numIndexes * numIndexes;
                        uint32 numIndexes3 = numIndexes2 * numIndexes;
                        uint32 indexesInIndirect = numIndexes;
                        uint32 indexesInDoubleIndirect = indexesInIndirect
                                + numIndexes2;
                        // uint32 indexesInTripleIndirect = indexesInDoubleIndirect
                                // + numIndexes3;

                        uint32 doubleIndirectBlock = EXT2_DIRECT_BLOCKS + 1;
                        uint32 indirectBlock = EXT2_DIRECT_BLOCKS;

                        CachedBlock cached(fVolume);
                        uint32* indirectData;

                        if (numBlocks > indexesInDoubleIndirect) {
                                // Triple indirect blocks
                                indirectData = (uint32*)cached.SetTo(EXT2_DIRECT_BLOCKS + 2);
                                if (indirectData == NULL)
                                        return B_IO_ERROR;

                                uint32 index = (numBlocks - indexesInDoubleIndirect)
                                        / numIndexes3;
                                doubleIndirectBlock = indirectData[index];
                        }

                        if (numBlocks > indexesInIndirect) {
                                // Double indirect blocks
                                indirectData = (uint32*)cached.SetTo(doubleIndirectBlock);
                                if (indirectData == NULL)
                                        return B_IO_ERROR;

                                uint32 index = (numBlocks - indexesInIndirect) / numIndexes2;
                                indirectBlock = indirectData[index];
                        }

                        indirectData = (uint32*)cached.SetTo(indirectBlock);
                                if (indirectData == NULL)
                                        return B_IO_ERROR;

                        uint32 index = numBlocks / numIndexes;
                        lastBlock = indirectData[index];
                }

                group = (lastBlock - fFirstBlock) / fBlocksPerGroup;
        }

        // TODO: Apply some more policies

        return AllocateBlocks(transaction, minimum, minimum + 8, group, start,
                allocated);
}


status_t
BlockAllocator::Free(Transaction& transaction, fsblock_t start, uint32 length)
{
        TRACE("BlockAllocator::Free(%" B_PRIu64 ", %" B_PRIu32 ")\n", start,
                length);
        MutexLocker lock(fLock);

        if (start <= fFirstBlock) {
                panic("Trying to free superblock!\n");
                return B_BAD_VALUE;
        }

        if (length == 0)
                return B_OK;
        if (start > fNumBlocks || length > fNumBlocks)
                return B_BAD_VALUE;

        TRACE("BlockAllocator::Free(): first block: %" B_PRIu64
                ", blocks per group: %" B_PRIu32 "\n", fFirstBlock, fBlocksPerGroup);

        start -= fFirstBlock;
        off_t end = start + length - 1;

        uint32 group = start / fBlocksPerGroup;
        if (group >= fNumGroups) {
                panic("BlockAllocator::Free() group %" B_PRIu32 " too big (fNumGroups "
                        "%" B_PRIu32 ")\n", group, fNumGroups);
        }
        uint32 lastGroup = end / fBlocksPerGroup;
        start = start % fBlocksPerGroup;

        if (group == lastGroup)
                return fGroups[group].Free(transaction, start, length);

        TRACE("BlockAllocator::Free(): Freeing from group %" B_PRIu32 ": %"
                B_PRIu64 ", %" B_PRIu64 "\n", group,
                start, fGroups[group].NumBits() - start);

        status_t status = fGroups[group].Free(transaction, start,
                fGroups[group].NumBits() - start);
        if (status != B_OK)
                return status;

        for (++group; group < lastGroup; ++group) {
                TRACE("BlockAllocator::Free(): Freeing all from group %" B_PRIu32 "\n",
                        group);
                status = fGroups[group].FreeAll(transaction);
                if (status != B_OK)
                        return status;
        }

        TRACE("BlockAllocator::Free(): Freeing from group %" B_PRIu32 ": 0-%"
                B_PRIu64 " \n", group, end % fBlocksPerGroup);
        return fGroups[group].Free(transaction, 0, (end + 1) % fBlocksPerGroup);
}


/*static*/ status_t
BlockAllocator::_Initialize(BlockAllocator* allocator)
{
        TRACE("BlockAllocator::_Initialize()\n");
        // fLock is already heald
        Volume* volume = allocator->fVolume;

        AllocationBlockGroup* groups = allocator->fGroups;
        uint32 numGroups = allocator->fNumGroups - 1;

        off_t freeBlocks = 0;
        TRACE("BlockAllocator::_Initialize(): free blocks: %" B_PRIdOFF "\n",
                freeBlocks);

        for (uint32 i = 0; i < numGroups; ++i) {
                status_t status = groups[i].Initialize(volume, i, 
                        allocator->fBlocksPerGroup);
                if (status != B_OK) {
                        mutex_unlock(&allocator->fLock);
                        return status;
                }

                freeBlocks += groups[i].FreeBits();
                TRACE("BlockAllocator::_Initialize(): free blocks: %" B_PRIdOFF "\n",
                        freeBlocks);
        }
        
        // Last block group may have less blocks
        status_t status = groups[numGroups].Initialize(volume, numGroups,
                allocator->fNumBlocks - allocator->fBlocksPerGroup * numGroups
                - allocator->fFirstBlock);
        if (status != B_OK) {
                mutex_unlock(&allocator->fLock);
                return status;
        }
        
        freeBlocks += groups[numGroups].FreeBits();

        TRACE("BlockAllocator::_Initialize(): free blocks: %" B_PRIdOFF "\n",
                freeBlocks);

        mutex_unlock(&allocator->fLock);

        if (freeBlocks != volume->NumFreeBlocks()) {
                TRACE("Counted free blocks (%" B_PRIdOFF ") doesn't match value in the"
                        " superblock (%" B_PRIdOFF ").\n", freeBlocks,
                        volume->NumFreeBlocks());
                return B_BAD_DATA;
        }

        return B_OK;
}