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


#include "ExtentStream.h"

#include <string.h>

#include "CachedBlock.h"
#include "Inode.h"
#include "Volume.h"


#undef ASSERT
//#define TRACE_EXT2
#ifdef TRACE_EXT2
#       define TRACE(x...) dprintf("\33[34mext2:\33[0m ExtentStream::" 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 ExtentStream::" x)


ExtentStream::ExtentStream(Volume* volume, Inode* inode,
        ext2_extent_stream* stream, off_t size)
        :
        fVolume(volume),
        fInode(inode),
        fStream(stream),
        fFirstBlock(volume->FirstDataBlock()),
        fAllocatedPos(fFirstBlock),
        fSize(size)
{
        fNumBlocks = size == 0 ? 0 : ((size - 1) >> fVolume->BlockShift()) + 1;
}


ExtentStream::~ExtentStream()
{
}


status_t
ExtentStream::FindBlock(off_t offset, fsblock_t& block, uint32 *_count)
{
        fileblock_t index = offset >> fVolume->BlockShift();
        TRACE("FindBlock(%" B_PRIdOFF ", %" B_PRIu64 ")\n", offset, index);

        if (offset >= fSize) {
                TRACE("FindBlock: offset larger than inode size\n");
                return B_ENTRY_NOT_FOUND;
        }

        ext2_extent_stream *stream = fStream;
        if (!stream->extent_header.IsValid())
                panic("ExtentStream::FindBlock() invalid header\n");

        CachedBlock cached(fVolume);
        while (stream->extent_header.Depth() != 0) {
                TRACE("FindBlock() depth %d\n",
                        stream->extent_header.Depth());
                int32 i = 1;
                while (i < stream->extent_header.NumEntries()
                        && stream->extent_index[i].LogicalBlock() <= index) {
                        i++;
                }
                TRACE("FindBlock() getting index %" B_PRId32 " at %" B_PRIu64 "\n",
                        i - 1, stream->extent_index[i - 1].PhysicalBlock());
                stream = (ext2_extent_stream *)cached.SetTo(
                        stream->extent_index[i - 1].PhysicalBlock());
                if (!stream->extent_header.IsValid())
                        panic("ExtentStream::FindBlock() invalid header\n");
                if (!fInode->VerifyExtentChecksum(stream)) {
                        panic("ExtentStream::FindBlock() invalid checksum\n");
                        return B_BAD_DATA;
                }
        }

        // find the extend following the one that should contain the logical block
        int32 extentIndex;
        if (stream->extent_header.NumEntries() > 7) {
                // binary search when enough entries
                int32 low = 0;
                int32 high = stream->extent_header.NumEntries() - 1;
                while (low < high) {
                        int32 middle = (high + low + 1) / 2;
                        if (stream->extent_entries[middle].LogicalBlock() > index)
                                high = middle - 1;
                        else
                                low = middle;
                }

                extentIndex = low + 1;
        } else {
                extentIndex = stream->extent_header.NumEntries();
                for (int32 i = 0; i < stream->extent_header.NumEntries(); i++) {
                        if (stream->extent_entries[i].LogicalBlock() > index) {
                                extentIndex = i;
                                break;
                        }
                }
        }

        fileblock_t logicalEndIndex
                = (fSize + fVolume->BlockSize() - 1) >> fVolume->BlockShift();

        if (extentIndex == 0) {
                // sparse block at the beginning of the stream
                block = 0;
                if (_count != NULL) {
                        *_count = stream->extent_header.NumEntries() == 0
                                ? logicalEndIndex - index
                                : stream->extent_entries[0].LogicalBlock() - index;
                }
                TRACE("FindBlock() sparse block index %" B_PRIu64 " at beginning of "
                        "stream\n", index);
                return B_OK;
        }

        const ext2_extent_entry& extent = stream->extent_entries[extentIndex - 1];
                // the extent supposedly containing the offset
        fileblock_t diff = index - extent.LogicalBlock();
        if (diff >= extent.Length()) {
                // sparse block between extends or at the end of the stream
                TRACE("FindBlock() sparse block index %" B_PRIu64 " at %" B_PRIu32
                        "\n", index, extent.LogicalBlock());
                block = 0;
                if (_count != NULL) {
                        *_count = stream->extent_header.NumEntries() == extentIndex
                                ? logicalEndIndex - index
                                : stream->extent_entries[extentIndex].LogicalBlock() - index;
                }
                return B_OK;
        }

        block = extent.PhysicalBlock() + diff;
        if (_count != NULL)
                *_count = extent.Length() - diff;

        TRACE("FindBlock(offset %" B_PRIdOFF "): %" B_PRIu64 " %" B_PRIu32
                "\n", offset, block, _count != NULL ? *_count : 1);

        return B_OK;
}


status_t
ExtentStream::Enlarge(Transaction& transaction, off_t& numBlocks)
{
        TRACE("Enlarge(): current size: %" B_PRIdOFF ", target size: %" B_PRIdOFF
                "\n", fNumBlocks, numBlocks);
        
        off_t targetBlocks = numBlocks;
        numBlocks = targetBlocks - fNumBlocks;
        uint32 allocated = 0;

        while (fNumBlocks < targetBlocks) {
                // allocate new blocks
                uint32 blockGroup = (fAllocatedPos - fFirstBlock)
                                / fVolume->BlocksPerGroup();
                
                if (allocated == 0) {
                        status_t status = fVolume->AllocateBlocks(transaction, 1,
                                targetBlocks - fNumBlocks, blockGroup, fAllocatedPos,
                                allocated);
                        if (status != B_OK) {
                                ERROR("Enlarge(): AllocateBlocks() failed()\n");
                                return status;
                        }
                }

                ASSERT(_CheckBlock(fStream, fAllocatedPos) == B_OK);

                ext2_extent_stream *stream = fStream;
                fsblock_t path[stream->extent_header.Depth()];
        
                // search for the leaf
                CachedBlock cached(fVolume);
                int32 level = -1;
                for (; stream->extent_header.Depth() != 0;) {
                        if (stream->extent_header.NumEntries() == 0)
                                panic("stream->extent_header.NumEntries() == 0\n");
                        int32 lastIndex = stream->extent_header.NumEntries() - 1;
                        TRACE("Enlarge() depth %d\n", stream->extent_header.Depth());
                        TRACE("Enlarge() getting index %" B_PRId32 " at %" B_PRIu64 "\n",
                                lastIndex, stream->extent_index[lastIndex].PhysicalBlock());
                        path[++level] = stream->extent_index[lastIndex].PhysicalBlock();
                        stream = (ext2_extent_stream *)cached.SetTo(path[level]);
                        if (stream == NULL)
                                return B_IO_ERROR;
                }

                // check if the new extent is adjacent
                if (stream->extent_header.NumEntries() > 0) {
                        ext2_extent_entry &last = stream->extent_entries[
                                stream->extent_header.NumEntries() - 1];
                        TRACE("Enlarge() last %" B_PRIu64 " allocatedPos %" B_PRIu64 "\n",
                                last.PhysicalBlock() + last.Length(), fAllocatedPos);
                        if (last.PhysicalBlock() + last.Length() == fAllocatedPos
                                && (last.Length() + allocated) <= EXT2_EXTENT_MAX_LENGTH) {
                                if (stream != fStream) {
                                        stream = (ext2_extent_stream *)cached.SetToWritable(
                                                transaction, cached.BlockNumber());
                                        if (stream == NULL)
                                                return B_IO_ERROR;
                                }
                                stream->extent_entries[stream->extent_header.NumEntries() - 1]
                                        .SetLength(last.Length() + allocated);
                                fInode->SetExtentChecksum(stream);
                                fNumBlocks += allocated;
                                allocated = 0;
                                TRACE("Enlarge() entry extended\n");
                                continue;
                        }
                }

                if (stream->extent_header.NumEntries()
                        >= stream->extent_header.MaxEntries()) {
                        TRACE("Enlarge() adding leaf and indexes at depth %d level %"
                                B_PRId32 "\n", stream->extent_header.Depth(), level);
                        // try to add a leaf and indexes
                        while (--level >= 0) {
                                stream = (ext2_extent_stream *)cached.SetTo(path[level]);
                                if (stream == NULL)
                                        return B_IO_ERROR;
                                if (stream->extent_header.NumEntries()
                                        < stream->extent_header.MaxEntries()) {
                                        break;
                                }
                                TRACE("Enlarge() going up from level %" B_PRId32 "\n", level);
                        }

                        if (level < 0 && fStream->extent_header.NumEntries()
                                        >= fStream->extent_header.MaxEntries()) {
                                TRACE("Enlarge() add a level, increment root depth\n");
                                fsblock_t newBlock = fStream->extent_index[0].PhysicalBlock();
                                uint32 _allocated;
                                status_t status = fVolume->AllocateBlocks(transaction, 1, 1,
                                        blockGroup, newBlock, _allocated);
                                if (status != B_OK) {
                                        ERROR("Enlarge() AllocateBlocks() failed()\n");
                                        return status;
                                }
                                ASSERT(_CheckBlock(fStream, newBlock) == B_OK);
                                TRACE("Enlarge() move root to block %" B_PRIu64 "\n",
                                        newBlock);
                                numBlocks++;
                                stream = (ext2_extent_stream *)cached.SetToWritable(
                                        transaction, newBlock);
                                if (stream == NULL)
                                        return B_IO_ERROR;
                                ASSERT(fStream->extent_header.IsValid());
                                memcpy(stream, fStream, sizeof(ext2_extent_stream));
                                fStream->extent_header.SetDepth(stream->extent_header.Depth()
                                        + 1);
                                TRACE("Enlarge() new root depth %d\n",
                                        fStream->extent_header.Depth());
                                fStream->extent_header.SetNumEntries(1);
                                fStream->extent_index[0].SetLogicalBlock(0);
                                fStream->extent_index[0].SetPhysicalBlock(newBlock);
                                stream->extent_header.SetMaxEntries((fVolume->BlockSize()
                                        - sizeof(ext2_extent_header)) / sizeof(ext2_extent_index));
                                fInode->SetExtentChecksum(stream);
                                ASSERT(stream->extent_header.IsValid());

                                ASSERT(Check());
                                continue;
                        }

                        if (level < 0)
                                stream = fStream;

                        uint16 depth = stream->extent_header.Depth();
                        while (depth > 1) {
                                TRACE("Enlarge() adding an index block at depth %d\n", depth);
                                fsblock_t newBlock = path[level];
                                uint32 _allocated;
                                status_t status = fVolume->AllocateBlocks(transaction, 1, 1,
                                        blockGroup, newBlock, _allocated);
                                if (status != B_OK) {
                                        ERROR("Enlarge(): AllocateBlocks() failed()\n");
                                        return status;
                                }
                                ASSERT(_CheckBlock(fStream, newBlock) == B_OK);
                                numBlocks++;
                                int32 index = stream->extent_header.NumEntries();
                                stream->extent_index[index].SetLogicalBlock(fNumBlocks);
                                stream->extent_index[index].SetPhysicalBlock(newBlock);
                                stream->extent_header.SetNumEntries(index + 1);
                                fInode->SetExtentChecksum(stream);
                                path[level++] = newBlock;

                                depth = stream->extent_header.Depth() - 1;
                                TRACE("Enlarge() init index block %" B_PRIu64 " at depth %d\n",
                                        newBlock, depth);
                                stream = (ext2_extent_stream *)cached.SetToWritable(
                                        transaction, newBlock);
                                if (stream == NULL)
                                        return B_IO_ERROR;
                                stream->extent_header.magic = EXT2_EXTENT_MAGIC;
                                stream->extent_header.SetNumEntries(0);
                                stream->extent_header.SetMaxEntries((fVolume->BlockSize()
                                        - sizeof(ext2_extent_header)) / sizeof(ext2_extent_index));
                                stream->extent_header.SetDepth(depth);
                                stream->extent_header.SetGeneration(0);
                                fInode->SetExtentChecksum(stream);

                                ASSERT(Check());
                        }

                        TRACE("Enlarge() depth %d level %" B_PRId32 "\n", 
                                stream->extent_header.Depth(), level);

                        if (stream->extent_header.Depth() == 1) {
                                TRACE("Enlarge() adding an entry block at depth %d level %"
                                        B_PRId32 "\n", depth, level);
                                fsblock_t newBlock;
                                if (level >= 0)
                                        newBlock = path[level];
                                else
                                        newBlock = fStream->extent_index[0].PhysicalBlock();
                                uint32 _allocated;
                                status_t status = fVolume->AllocateBlocks(transaction, 1, 1,
                                        blockGroup, newBlock, _allocated);
                                if (status != B_OK) {
                                        ERROR("Enlarge(): AllocateBlocks() failed()\n");
                                        return status;
                                }

                                ASSERT(_CheckBlock(fStream, newBlock) == B_OK);
                                numBlocks++;
                                int32 index = stream->extent_header.NumEntries();
                                stream->extent_index[index].SetLogicalBlock(fNumBlocks);
                                stream->extent_index[index].SetPhysicalBlock(newBlock);
                                stream->extent_header.SetNumEntries(index + 1);
                                fInode->SetExtentChecksum(stream);

                                TRACE("Enlarge() init entry block %" B_PRIu64
                                        " at depth %d\n", newBlock, depth);
                                stream = (ext2_extent_stream *)cached.SetToWritable(
                                        transaction, newBlock);
                                if (stream == NULL)
                                        return B_IO_ERROR;
                                stream->extent_header.magic = EXT2_EXTENT_MAGIC;
                                stream->extent_header.SetNumEntries(0);
                                stream->extent_header.SetMaxEntries((fVolume->BlockSize()
                                        - sizeof(ext2_extent_header)) / sizeof(ext2_extent_entry));
                                stream->extent_header.SetDepth(0);
                                stream->extent_header.SetGeneration(0);
                                fInode->SetExtentChecksum(stream);
                                ASSERT(Check());
                        }
                }
                
                // add a new entry
                TRACE("Enlarge() add entry %" B_PRIu64 "\n", fAllocatedPos);
                if (stream != fStream) {
                        stream = (ext2_extent_stream *)cached.SetToWritable(
                                transaction, cached.BlockNumber());
                        if (stream == NULL)
                                return B_IO_ERROR;
                }
                int32 index = stream->extent_header.NumEntries();
                stream->extent_entries[index].SetLogicalBlock(fNumBlocks);
                stream->extent_entries[index].SetLength(allocated);
                stream->extent_entries[index].SetPhysicalBlock(fAllocatedPos);
                stream->extent_header.SetNumEntries(index + 1);
                fInode->SetExtentChecksum(stream);
                TRACE("Enlarge() entry added at index %" B_PRId32 "\n", index);
                ASSERT(stream->extent_header.IsValid());

                fNumBlocks += allocated;
                allocated = 0;
        }
        
        return B_OK;
}


status_t
ExtentStream::Shrink(Transaction& transaction, off_t& numBlocks)
{
        TRACE("DataStream::Shrink(): current size: %" B_PRIdOFF ", target size: %"
                B_PRIdOFF "\n", fNumBlocks, numBlocks);

        off_t targetBlocks = numBlocks;
        numBlocks = fNumBlocks - targetBlocks;

        while (targetBlocks < fNumBlocks) {
                ext2_extent_stream *stream = fStream;
                fsblock_t path[stream->extent_header.Depth()];
        
                // search for the leaf
                CachedBlock cached(fVolume);
                int32 level = -1;
                for (; stream->extent_header.Depth() != 0;) {
                        if (stream->extent_header.NumEntries() == 0)
                                panic("stream->extent_header.NumEntries() == 0\n");
                        int32 lastIndex = stream->extent_header.NumEntries() - 1;
                        TRACE("Shrink() depth %d\n", stream->extent_header.Depth());
                        TRACE("Shrink() getting index %" B_PRId32 " at %" B_PRIu64 "\n",
                                lastIndex, stream->extent_index[lastIndex].PhysicalBlock());
                        path[++level] = stream->extent_index[lastIndex].PhysicalBlock();
                        stream = (ext2_extent_stream *)cached.SetToWritable(transaction, 
                                path[level]);
                        if (stream == NULL)
                                return B_IO_ERROR;
                        if (!stream->extent_header.IsValid())
                                panic("Shrink() invalid header\n");
                }
                
                int32 index = stream->extent_header.NumEntries() - 1;
                status_t status = B_OK;
                while (index >= 0 && targetBlocks < fNumBlocks) {
                        ext2_extent_entry &last = stream->extent_entries[index];
                        if (last.LogicalBlock() + last.Length() < targetBlocks) {
                                status = B_BAD_DATA;
                                break;
                        }
                        uint16 length = min_c(last.Length(), fNumBlocks - targetBlocks);
                        fsblock_t block = last.PhysicalBlock() + last.Length() - length;
                        TRACE("Shrink() free block %" B_PRIu64 " length %d\n", block,
                                length); 
                        status = fVolume->FreeBlocks(transaction, block, length);
                        if (status != B_OK)
                                break;
                        fNumBlocks -= length;
                        stream->extent_entries[index].SetLength(last.Length() - length);
                        TRACE("Shrink() new length for %" B_PRId32 ": %d\n", index, last.Length());
                        if (last.Length() != 0)
                                break;
                        index--;
                        TRACE("Shrink() next index: %" B_PRId32 "\n", index);
                }
                TRACE("Shrink() new entry count: %" B_PRId32 "\n", index + 1);
                stream->extent_header.SetNumEntries(index + 1);
                fInode->SetExtentChecksum(stream);
                ASSERT(Check());
                
                if (status != B_OK)
                        return status;

                while (stream != fStream && stream->extent_header.NumEntries() == 0) {
                        TRACE("Shrink() empty leaf at depth %d\n", 
                                stream->extent_header.Depth());
                        level--;
                        if (level >= 0) {
                                stream = (ext2_extent_stream *)cached.SetToWritable(
                                        transaction, path[level]);
                                if (stream == NULL)
                                        return B_IO_ERROR;
                        } else
                                stream = fStream;
                        if (!stream->extent_header.IsValid())
                                panic("Shrink() invalid header\n");
                        uint16 index = stream->extent_header.NumEntries() - 1;
                        ext2_extent_index &last = stream->extent_index[index];
                        if (last.PhysicalBlock() != path[level + 1])
                                panic("Shrink() last.PhysicalBlock() != path[level + 1]\n");
                        status = fVolume->FreeBlocks(transaction, last.PhysicalBlock(), 1);
                        if (status != B_OK)
                                return status;
                        numBlocks++;
                        stream->extent_header.SetNumEntries(index);
                        fInode->SetExtentChecksum(stream);
                        TRACE("Shrink() new entry count: %d\n", index);
                }
                if (stream == fStream && stream->extent_header.NumEntries() == 0)
                        stream->extent_header.SetDepth(0);

                ASSERT(Check());
        }

        return B_OK;
}


void
ExtentStream::Init()
{
        fStream->extent_header.magic = EXT2_EXTENT_MAGIC;
        fStream->extent_header.SetNumEntries(0);
        fStream->extent_header.SetMaxEntries(4);
        fStream->extent_header.SetDepth(0);
        fStream->extent_header.SetGeneration(0);
        fInode->SetExtentChecksum(fStream);
}


status_t
ExtentStream::_Check(ext2_extent_stream *stream, fileblock_t &block)
{
        if (!stream->extent_header.IsValid()) {
                panic("_Check() invalid header\n");
                return B_BAD_VALUE;
        }
        if (stream->extent_header.Depth() == 0) {
                for (int32 i = 0; i < stream->extent_header.NumEntries(); i++) {
                        ext2_extent_entry &entry = stream->extent_entries[i];
                        if (entry.LogicalBlock() < block) {
                                panic("_Check() entry %" B_PRId32 " %" B_PRIu64 " %" B_PRIu32
                                        "\n", i, block, entry.LogicalBlock());
                                return B_BAD_VALUE;
                        }
                        block = entry.LogicalBlock() + entry.Length();
                }
                return B_OK;
        } 

        CachedBlock cached(fVolume);
        for (int32 i = 0; i < stream->extent_header.NumEntries(); i++) {
                ext2_extent_index &index = stream->extent_index[i];
                if (index.LogicalBlock() < block) {
                        panic("_Check() index %" B_PRId32 " %" B_PRIu64 " %" B_PRIu32
                                "\n", i, block, index.LogicalBlock());
                        return B_BAD_VALUE;
                }
                ext2_extent_stream *child = (ext2_extent_stream *)cached.SetTo(
                        index.PhysicalBlock());
                if (child->extent_header.Depth() != (stream->extent_header.Depth() - 1) 
                        || _Check(child, block) != B_OK)
                        return B_BAD_VALUE;
        }
        return B_OK;
}


bool
ExtentStream::Check()
{
        fileblock_t block = 0;
        return _Check(fStream, block) == B_OK;
}


status_t
ExtentStream::_CheckBlock(ext2_extent_stream *stream, fsblock_t block)
{
        if (stream->extent_header.Depth() == 0) {
                for (int32 i = 0; i < stream->extent_header.NumEntries(); i++) {
                        ext2_extent_entry &entry = stream->extent_entries[i];
                        if (entry.PhysicalBlock() <= block
                                && (entry.PhysicalBlock() + entry.Length()) > block) {
                                panic("_CheckBlock() entry %" B_PRId32 " %" B_PRIu64 " %"
                                        B_PRIu64 " %d\n", i, block, entry.PhysicalBlock(),
                                        entry.Length());
                                return B_BAD_VALUE;
                        }
                }
                return B_OK;
        }

        CachedBlock cached(fVolume);
        for (int32 i = 0; i < stream->extent_header.NumEntries(); i++) {
                ext2_extent_index &index = stream->extent_index[i];
                if (index.PhysicalBlock() == block) {
                        panic("_CheckBlock() index %" B_PRId32 " %" B_PRIu64 "\n", i, block);
                        return B_BAD_VALUE;
                }
                ext2_extent_stream *child = (ext2_extent_stream *)cached.SetTo(
                        index.PhysicalBlock());
                if (child->extent_header.Depth() != (stream->extent_header.Depth() - 1)
                        || _CheckBlock(child, block) != B_OK)
                        return B_BAD_VALUE;
        }
        return B_OK;
}