/*
 * COPYRIGHT:        See COPYRIGHT.TXT
 * PROJECT:          Ext2 File System Driver for WinNT/2K/XP
 * FILE:             indirect.c
 * PROGRAMMER:       Matt Wu <mattwu@163.com>
 * HOMEPAGE:         http://www.ext2fsd.com
 * UPDATE HISTORY:
 */

/* INCLUDES *****************************************************************/

#include "ext2fs.h"

/* GLOBALS *****************************************************************/

extern PEXT2_GLOBAL Ext2Global;

/* DEFINITIONS *************************************************************/

#ifdef ALLOC_PRAGMA
#endif


NTSTATUS
Ext2ExpandLast(
    IN PEXT2_IRP_CONTEXT    IrpContext,
    IN PEXT2_VCB            Vcb,
    IN PEXT2_MCB            Mcb,
    IN ULONG                Base,
    IN ULONG                Layer,
    IN PULONG *             Data,
    IN PULONG               Hint,
    IN PULONG               Block,
    IN OUT PULONG           Number
)
{
    PULONG      pData = NULL;
    ULONG       i;
    NTSTATUS    Status = STATUS_SUCCESS;

    if (Layer > 0 || IsMcbDirectory(Mcb)) {

        /* allocate buffer for new block */
        pData = (ULONG *) Ext2AllocatePool(
                    PagedPool,
                    BLOCK_SIZE,
                    EXT2_DATA_MAGIC
                );
        if (!pData) {
            DEBUG(DL_ERR, ( "Ex2ExpandBlock: failed to allocate memory for Data.\n"));
            Status = STATUS_INSUFFICIENT_RESOURCES;
            goto errorout;
        }
        RtlZeroMemory(pData, BLOCK_SIZE);
        INC_MEM_COUNT(PS_BLOCK_DATA, pData, BLOCK_SIZE);
    }

    /* allocate block from disk */
    Status = Ext2NewBlock(
                 IrpContext,
                 Vcb,
                 (Mcb->Inode.i_ino - 1) / BLOCKS_PER_GROUP,
                 *Hint,
                 Block,
                 Number
             );

    if (!NT_SUCCESS(Status)) {
        goto errorout;
    }

    /* increase inode i_blocks */
    Mcb->Inode.i_blocks += (*Number << (BLOCK_BITS - 9));

    if (Layer == 0) {

        if (IsMcbDirectory(Mcb)) {
            /* for directory we need initialize it's entry structure */
            PEXT2_DIR_ENTRY2 pEntry;
            pEntry = (PEXT2_DIR_ENTRY2) pData;
            pEntry->rec_len = (USHORT)(BLOCK_SIZE);
            ASSERT(*Number == 1);
            Ext2SaveBlock(IrpContext, Vcb, *Block, (PVOID)pData);
        }

        /* add new Extent into Mcb */
        if (!Ext2AddBlockExtent(Vcb, Mcb, Base, (*Block), *Number)) {
            DbgBreak();
            ClearFlag(Mcb->Flags, MCB_ZONE_INITED);
            Ext2ClearAllExtents(&Mcb->Extents);
        }

    } else {

        /* zero the content of all meta blocks */
        for (i = 0; i < *Number; i++) {
            Ext2SaveBlock(IrpContext, Vcb, *Block + i, (PVOID)pData);
            /* add block to meta extents */
            if (!Ext2AddMcbMetaExts(Vcb, Mcb, *Block + i, 1)) {
                DbgBreak();
                Ext2Sleep(500);
                Ext2AddMcbMetaExts(Vcb, Mcb, *Block + i, 1);
            }
        }
    }

errorout:

    if (NT_SUCCESS(Status)) {
        *Hint = *Block + *Number;
        if (Data) {
            *Data = pData;
            ASSERT(*Number == 1);
        } else {
            if (pData) {
                Ext2FreePool(pData, EXT2_DATA_MAGIC);
                DEC_MEM_COUNT(PS_BLOCK_DATA, pData, BLOCK_SIZE);
            }
        }
    } else {
        if (pData) {
            Ext2FreePool(pData, EXT2_DATA_MAGIC);
            DEC_MEM_COUNT(PS_BLOCK_DATA, pData, BLOCK_SIZE);
        }
        if (*Block) {
            Ext2FreeBlock(IrpContext, Vcb, *Block, *Number);
            Mcb->Inode.i_blocks -= (*Number << (BLOCK_BITS - 9));
            *Block = 0;
        }
    }

    return Status;
}

NTSTATUS
Ext2GetBlock(
    IN PEXT2_IRP_CONTEXT    IrpContext,
    IN PEXT2_VCB            Vcb,
    IN PEXT2_MCB            Mcb,
    IN ULONG                Base,
    IN ULONG                Layer,
    IN ULONG                Start,
    IN ULONG                SizeArray,
    IN PULONG               BlockArray,
    IN BOOLEAN              bAlloc,
    IN OUT PULONG           Hint,
    OUT PULONG              Block,
    OUT PULONG              Number
)
{
    NTSTATUS    Status = STATUS_SUCCESS;
    PBCB        Bcb = NULL;
    PULONG      pData = NULL;
    ULONG       Slot = 0, i = 0;
    ULONG       Unit = 1;

    LARGE_INTEGER Offset;

    if (Layer == 0) {

        *Number = 1;
        if (BlockArray[0] == 0 && bAlloc) {

            /* now allocate new block */
            Status = Ext2ExpandLast(
                         IrpContext,
                         Vcb,
                         Mcb,
                         Base,
                         Layer,
                         NULL,
                         Hint,
                         &BlockArray[0],
                         Number
                     );

            if (!NT_SUCCESS(Status)) {
                goto errorout;
            }
        } else {
            /* check the block is valid or not */
            if (BlockArray[0] >= TOTAL_BLOCKS) {
                DbgBreak();
                Status = STATUS_DISK_CORRUPT_ERROR;
                goto errorout;
            }
        }

        *Block = BlockArray[0];
        for (i=1; i < SizeArray; i++) {
            if (BlockArray[i] == BlockArray[i-1] + 1) {
                *Number = *Number + 1;
            } else {
                break;
            }
        }
        *Hint = BlockArray[*Number - 1];

    } else if (Layer <= 3) {

        /* check the block is valid or not */
        if (BlockArray[0] == 0 || BlockArray[0] >= TOTAL_BLOCKS) {
            DbgBreak();
            Status = STATUS_DISK_CORRUPT_ERROR;
            goto errorout;
        }

        /* add block to meta extents */
        if (!Ext2AddMcbMetaExts(Vcb, Mcb, BlockArray[0], 1)) {
            DbgBreak();
            Ext2Sleep(500);
            Ext2AddMcbMetaExts(Vcb, Mcb, BlockArray[0], 1);
        }

        /* map memory in cache for the index block */
        Offset.QuadPart = ((LONGLONG)BlockArray[0]) << BLOCK_BITS;
        if ( !CcPinRead( Vcb->Volume,
                         (PLARGE_INTEGER) (&Offset),
                         BLOCK_SIZE,
                         PIN_WAIT,
                         &Bcb,
                         &pData )) {

            DEBUG(DL_ERR, ( "Ext2GetBlock: Failed to PinLock block: %xh ...\n",
                            BlockArray[0] ));
            Status = STATUS_CANT_WAIT;
            goto errorout;
        }

        if (Layer > 1) {
            Unit = Vcb->max_blocks_per_layer[Layer - 1];
        } else {
            Unit = 1;
        }

        Slot  = Start / Unit;
        Start = Start % Unit;

        if (pData[Slot] == 0) {

            if (bAlloc) {

                /* we need allocate new block and zero all data in case
                   it's an in-direct block. Index stores the new block no. */
                ULONG   Count = 1;
                Status = Ext2ExpandLast(
                             IrpContext,
                             Vcb,
                             Mcb,
                             Base,
                             Layer,
                             NULL,
                             Hint,
                             &pData[Slot],
                             &Count
                         );

                if (!NT_SUCCESS(Status)) {
                    goto errorout;
                }

                /* refresh hint block */
                *Hint = pData[Slot];

                /* set dirty bit to notify system to flush */
                CcSetDirtyPinnedData(Bcb, NULL );
                SetFlag(Vcb->Volume->Flags, FO_FILE_MODIFIED);
                if (!Ext2AddVcbExtent(Vcb, Offset.QuadPart,
                                      (LONGLONG)BLOCK_SIZE)) {
                    DbgBreak();
                    Ext2Sleep(100);
                    if (!Ext2AddVcbExtent(Vcb, Offset.QuadPart,
                                          (LONGLONG)BLOCK_SIZE)) {
                        Status = STATUS_INSUFFICIENT_RESOURCES;
                        goto errorout;
                    }
                }

                /* save inode information here */
                Ext2SaveInode(IrpContext, Vcb, &Mcb->Inode);

            } else {

                *Number = 1;

                if (Layer == 1) {
                    for (i = Slot + 1; i < BLOCK_SIZE/4; i++) {
                        if (pData[i] == 0) {
                            *Number = *Number + 1;
                        } else {
                            break;
                        }
                    }
                } else if (Layer == 2) {
                    *Number = BLOCK_SIZE/4 - Start;
                } else {
                    *Number = BLOCK_SIZE/4;
                }

                goto errorout;
            }
        }

        /* transfer to next recursion call */
        Status = Ext2GetBlock(
                     IrpContext,
                     Vcb,
                     Mcb,
                     Base,
                     Layer - 1,
                     Start,
                     BLOCK_SIZE/4 - Slot,
                     &pData[Slot],
                     bAlloc,
                     Hint,
                     Block,
                     Number
                 );

        if (!NT_SUCCESS(Status)) {
            goto errorout;
        }
    }

errorout:

    /* free the memory of pData */
    if (Bcb) {
        CcUnpinData(Bcb);
    }

    if (!NT_SUCCESS(Status)) {
        *Block = 0;
    }

    return Status;
}


NTSTATUS
Ext2ExpandBlock(
    IN PEXT2_IRP_CONTEXT IrpContext,
    IN PEXT2_VCB         Vcb,
    IN PEXT2_MCB         Mcb,
    IN ULONG             Base,
    IN ULONG             Layer,
    IN ULONG             Start,
    IN ULONG             SizeArray,
    IN PULONG            BlockArray,
    IN PULONG            Hint,
    IN PULONG            Extra
)
{
    ULONG       i = 0;
    ULONG       j;
    ULONG       Slot;
    ULONG       Block = 0;
    LARGE_INTEGER Offset;

    PBCB        Bcb = NULL;
    PULONG      pData = NULL;
    ULONG       Skip = 0;

    ULONG       Number;
    ULONG       Wanted;

    NTSTATUS    Status = STATUS_SUCCESS;

    if (Layer == 1) {

        /*
         * try to make all leaf block continuous to avoid fragments
         */

        Number = min(SizeArray, ((*Extra + (Start & (BLOCK_SIZE/4 - 1))) * 4 / BLOCK_SIZE));
        Wanted = 0;
        DEBUG(DL_BLK, ("Ext2ExpandBlock: SizeArray=%xh Extra=%xh Start=%xh %xh\n",
                       SizeArray, *Extra, Start, Number ));

        for (i=0; i < Number; i++) {
            if (BlockArray[i] == 0) {
                Wanted += 1;
            }
        }

        i = 0;
        while (Wanted > 0) {

            Number = Wanted;
            Status = Ext2ExpandLast(
                         IrpContext,
                         Vcb,
                         Mcb,
                         Base,
                         Layer,
                         NULL,
                         Hint,
                         &Block,
                         &Number
                     );
            if (!NT_SUCCESS(Status)) {
                goto errorout;
            }

            ASSERT(Number > 0);
            Wanted -= Number;
            while (Number) {
                if (BlockArray[i] == 0) {
                    BlockArray[i] = Block++;
                    Number--;
                }
                i++;
            }
        }

    } else if (Layer == 0) {

        /*
         * bulk allocation for inode data blocks
         */

        i = 0;

        while (*Extra && i < SizeArray) {

            Wanted = 0;
            Number = 1;

            for (j = i; j < SizeArray && j < i + *Extra; j++) {

                if (BlockArray[j] >= TOTAL_BLOCKS) {
                    DbgBreak();
                    BlockArray[j] = 0;
                }

                if (BlockArray[j] == 0) {
                    Wanted += 1;
                } else {
                    break;
                }
            }

            if (Wanted == 0) {

                /* add block extent into Mcb */
                ASSERT(BlockArray[i] != 0);
                if (!Ext2AddBlockExtent(Vcb, Mcb, Base + i, BlockArray[i], 1)) {
                    DbgBreak();
                    ClearFlag(Mcb->Flags, MCB_ZONE_INITED);
                    Ext2ClearAllExtents(&Mcb->Extents);
                }

            } else {

                Number = Wanted;
                Status = Ext2ExpandLast(
                             IrpContext,
                             Vcb,
                             Mcb,
                             Base + i,
                             0,
                             NULL,
                             Hint,
                             &Block,
                             &Number
                         );
                if (!NT_SUCCESS(Status)) {
                    goto errorout;
                }

                ASSERT(Number > 0);
                for (j = 0; j < Number; j++) {
                    BlockArray[i + j] = Block++;
                }
            }

            *Extra -= Number;
            i += Number;
        }

        goto errorout;
    }


    /*
     * only for meta blocks allocation
     */

    for (i = 0; *Extra && i < SizeArray; i++) {

        if (Layer <= 3) {

            if (BlockArray[i] >= TOTAL_BLOCKS) {
                DbgBreak();
                BlockArray[i] = 0;
            }

            if (BlockArray[i] == 0) {
                Number = 1;
                Status = Ext2ExpandLast(
                             IrpContext,
                             Vcb,
                             Mcb,
                             Base,
                             Layer,
                             &pData,
                             Hint,
                             &BlockArray[i],
                             &Number
                         );
                if (!NT_SUCCESS(Status)) {
                    goto errorout;
                }

            } else {

                Offset.QuadPart = (((LONGLONG)BlockArray[i]) << BLOCK_BITS);
                if (!CcPinRead(
                            Vcb->Volume,
                            &Offset,
                            BLOCK_SIZE,
                            PIN_WAIT,
                            &Bcb,
                            &pData )) {

                    DEBUG(DL_ERR, ( "Ext2ExpandInode: failed to PinLock offset :%I64xh...\n",
                                    Offset.QuadPart));
                    Status = STATUS_CANT_WAIT;
                    DbgBreak();
                    goto errorout;
                }

                /* add block to meta extents */
                if (!Ext2AddMcbMetaExts(Vcb, Mcb,  BlockArray[i], 1)) {
                    DbgBreak();
                    Ext2Sleep(500);
                    Ext2AddMcbMetaExts(Vcb, Mcb,  BlockArray[i], 1);
                }
            }

            Skip = Vcb->max_blocks_per_layer[Layer] * i;

            if (i == 0) {
                if (Layer > 1) {
                    Slot  = Start / Vcb->max_blocks_per_layer[Layer - 1];
                    Start = Start % Vcb->max_blocks_per_layer[Layer - 1];
                    Skip += Slot * Vcb->max_blocks_per_layer[Layer - 1];
                } else {
                    Slot  = Start;
                    Start = 0;
                    Skip += Slot;
                }
            } else {
                Start = 0;
                Slot  = 0;
            }

            Status = Ext2ExpandBlock(
                         IrpContext,
                         Vcb,
                         Mcb,
                         Base + Skip,
                         Layer - 1,
                         Start,
                         BLOCK_SIZE/4 - Slot,
                         &pData[Slot],
                         Hint,
                         Extra
                     );

            if (Bcb) {
                CcSetDirtyPinnedData(Bcb, NULL);
                if (!Ext2AddBlockExtent(Vcb, NULL,
                                        BlockArray[i],
                                        BlockArray[i], 1)) {
                    DbgBreak();
                    Ext2Sleep(500);
                    if (!Ext2AddBlockExtent(Vcb, NULL,
                                            BlockArray[i],
                                            BlockArray[i], 1)) {
                    }
                }
            } else {
                Ext2SaveBlock(IrpContext, Vcb, BlockArray[i], (PVOID)pData);
            }

            if (pData) {
                if (Bcb) {
                    CcUnpinData(Bcb);
                    Bcb = NULL;
                } else {
                    Ext2FreePool(pData, EXT2_DATA_MAGIC);
                    DEC_MEM_COUNT(PS_BLOCK_DATA, pData, BLOCK_SIZE);
                }
                pData = NULL;
            }

            if (!NT_SUCCESS(Status)) {
                DbgBreak();
                break;
            }
        }
    }

errorout:

    return Status;
}

BOOLEAN
Ext2IsBlockEmpty(PULONG BlockArray, ULONG SizeArray)
{
    ULONG i = 0;
    for (i=0; i < SizeArray; i++) {
        if (BlockArray[i]) {
            break;
        }
    }
    return (i == SizeArray);
}


NTSTATUS
Ext2TruncateBlock(
    IN PEXT2_IRP_CONTEXT IrpContext,
    IN PEXT2_VCB         Vcb,
    IN PEXT2_MCB         Mcb,
    IN ULONG             Base,
    IN ULONG             Start,
    IN ULONG             Layer,
    IN ULONG             SizeArray,
    IN PULONG            BlockArray,
    IN PULONG            Extra
)
{
    NTSTATUS    Status = STATUS_SUCCESS;
    ULONG       i = 0;
    ULONG       Slot = 0;
    ULONG       Skip = 0;

    LONGLONG    Offset;
    PBCB        Bcb = NULL;
    PULONG      pData = NULL;

    ASSERT(Mcb != NULL);

    for (i = 0; i < SizeArray; i++) {

        if (Layer == 0) {

            ULONG   Number = 1;

            while (Extra &&  SizeArray > i + 1 && Number < *Extra) {

                if (BlockArray[SizeArray - i - 1] ==
                    BlockArray[SizeArray - i - 2] + 1) {

                    BlockArray[SizeArray - i - 1] = 0;
                    Number++;
                    SizeArray--;

                } else {
                    break;
                }
            }

            if (BlockArray[SizeArray - i - 1]) {

                Status = Ext2FreeBlock(IrpContext, Vcb, BlockArray[SizeArray - i - 1], Number);
                if (NT_SUCCESS(Status)) {
                    ASSERT(Mcb->Inode.i_blocks >= (Number << (BLOCK_BITS - 9)));
                    if (Mcb->Inode.i_blocks < (Number << (BLOCK_BITS - 9))) {
                        Mcb->Inode.i_blocks = 0;
                        DbgBreak();
                    } else {
                        Mcb->Inode.i_blocks -= (Number << (BLOCK_BITS - 9));
                    }
                    BlockArray[SizeArray - i - 1] = 0;
                }
            }

            if (Extra) {

                /* dec blocks count */
                ASSERT(*Extra >= Number);
                *Extra = *Extra - Number;

                /* remove block mapping frm Mcb Extents */
                if (!Ext2RemoveBlockExtent(Vcb, Mcb, Base + SizeArray - 1 - i, Number)) {
                    DbgBreak();
                    ClearFlag(Mcb->Flags, MCB_ZONE_INITED);
                    Ext2ClearAllExtents(&Mcb->Extents);
                }
            }

        } else {

            ASSERT(Layer <= 3);

            if (BlockArray[SizeArray - i - 1] >= TOTAL_BLOCKS) {
                DbgBreak();
                BlockArray[SizeArray - i - 1] = 0;
            }

            if (i == 0) {
                if (Layer > 1) {
                    Slot  = Start / Vcb->max_blocks_per_layer[Layer - 1];
                    Start = Start % Vcb->max_blocks_per_layer[Layer - 1];
                } else {
                    Slot  = Start;
                    Start = (BLOCK_SIZE / 4) - 1;
                }
            } else {
                Slot = Start = (BLOCK_SIZE / 4) - 1;
            }

            Skip = (SizeArray - i - 1) * Vcb->max_blocks_per_layer[Layer];

            if (BlockArray[SizeArray - i - 1]) {

                Offset = (LONGLONG) (BlockArray[SizeArray - i - 1]);
                Offset = Offset << BLOCK_BITS;

                if (!CcPinRead( Vcb->Volume,
                                (PLARGE_INTEGER) (&Offset),
                                BLOCK_SIZE,
                                PIN_WAIT,
                                &Bcb,
                                &pData )) {

                    DEBUG(DL_ERR, ( "Ext2TruncateBlock: PinLock failed on block %xh ...\n",
                                    BlockArray[SizeArray - i - 1]));
                    Status = STATUS_CANT_WAIT;
                    DbgBreak();
                    goto errorout;
                }

                Status = Ext2TruncateBlock(
                             IrpContext,
                             Vcb,
                             Mcb,
                             Base + Skip,
                             Start,
                             Layer - 1,
                             Slot + 1,
                             &pData[0],
                             Extra
                         );

                if (!NT_SUCCESS(Status)) {
                    break;
                }

                CcSetDirtyPinnedData(Bcb, NULL);
                Ext2AddVcbExtent(Vcb, Offset, (LONGLONG)BLOCK_SIZE);

                if (*Extra || Ext2IsBlockEmpty(pData, BLOCK_SIZE/4)) {

                    Ext2TruncateBlock(
                                 IrpContext,
                                 Vcb,
                                 Mcb,
                                 Base + Skip,    /* base */
                                 0,              /* start */
                                 0,              /* layer */
                                 1,
                                 &BlockArray[SizeArray - i - 1],
                                 NULL
                             );

                    if (!Ext2RemoveMcbMetaExts(Vcb, Mcb, BlockArray[SizeArray - i - 1], 1)) {
                        DbgBreak();
                        Ext2Sleep(500);
                        Ext2RemoveMcbMetaExts(Vcb, Mcb, BlockArray[SizeArray - i - 1], 1);
                    }
                }

                if (pData) {
                    CcUnpinData(Bcb);
                    Bcb = NULL;
                    pData = NULL;
                }

            } else {

                if (Layer > 1) {
                    if (*Extra > Slot * Vcb->max_blocks_per_layer[Layer - 1] + Start + 1) {
                        *Extra -= (Slot * Vcb->max_blocks_per_layer[Layer - 1] + Start + 1);
                    } else {
                        *Extra  = 0;
                    }
                } else {
                    if (*Extra > Slot + 1) {
                        *Extra -= (Slot + 1);
                    } else {
                        *Extra  = 0;
                    }
                }

                if (!Ext2RemoveBlockExtent(Vcb, Mcb, Base + Skip, (Start + 1))) {
                    DbgBreak();
                    ClearFlag(Mcb->Flags, MCB_ZONE_INITED);
                    Ext2ClearAllExtents(&Mcb->Extents);
                }
            }
        }

        if (Extra && *Extra == 0) {
            break;
        }
    }

errorout:

    if (pData) {
        CcUnpinData(Bcb);
    }

    return Status;
}

NTSTATUS
Ext2MapIndirect(
    IN PEXT2_IRP_CONTEXT    IrpContext,
    IN PEXT2_VCB            Vcb,
    IN PEXT2_MCB            Mcb,
    IN ULONG                Index,
    IN BOOLEAN              bAlloc,
    OUT PULONG              pBlock,
    OUT PULONG              Number
)
{
    ULONG   Layer;
    ULONG   Slot;

    ULONG   Base = Index;

    NTSTATUS Status = STATUS_SUCCESS;

    *pBlock = 0;
    *Number = 0;

    for (Layer = 0; Layer < EXT2_BLOCK_TYPES; Layer++) {

        if (Index < Vcb->max_blocks_per_layer[Layer]) {

            ULONG   dwRet = 0, dwBlk = 0, dwHint = 0, dwArray = 0;

            Slot = (Layer==0) ? (Index):(Layer + EXT2_NDIR_BLOCKS - 1);
            dwBlk = Mcb->Inode.i_block[Slot];

            if (dwBlk == 0) {

                if (!bAlloc) {

                    *Number = 1;
                    goto errorout;

                } else {

                    if (Slot) {
                        dwHint = Mcb->Inode.i_block[Slot - 1];
                    }

                    /* allocate and zero block if necessary */
                    *Number = 1;
                    Status = Ext2ExpandLast(
                                 IrpContext,
                                 Vcb,
                                 Mcb,
                                 Base,
                                 Layer,
                                 NULL,
                                 &dwHint,
                                 &dwBlk,
                                 Number
                             );

                    if (!NT_SUCCESS(Status)) {
                        goto errorout;
                    }

                    /* save the it into inode*/
                    Mcb->Inode.i_block[Slot] = dwBlk;

                    /* save the inode */
                    if (!Ext2SaveInode(IrpContext, Vcb, &Mcb->Inode)) {
                        DbgBreak();
                        Status = STATUS_UNSUCCESSFUL;
                        goto errorout;
                    }
                }
            }

            if (Layer == 0) 
                dwArray = Vcb->max_blocks_per_layer[Layer] - Index;
            else
                dwArray = 1;

            /* querying block number of the index-th file block */
            Status = Ext2GetBlock(
                         IrpContext,
                         Vcb,
                         Mcb,
                         Base,
                         Layer,
                         Index,
                         dwArray,
                        &Mcb->Inode.i_block[Slot],
                         bAlloc,
                         &dwHint,
                         &dwRet,
                         Number
                     );

            if (NT_SUCCESS(Status)) {
                *pBlock = dwRet;
            }

            break;
        }

        Index -= Vcb->max_blocks_per_layer[Layer];
    }

errorout:

    return Status;
}

NTSTATUS
Ext2ExpandIndirect(
    PEXT2_IRP_CONTEXT IrpContext,
    PEXT2_VCB         Vcb,
    PEXT2_MCB         Mcb,
    ULONG             Start,
    ULONG             End,
    PLARGE_INTEGER    Size
)
{
    NTSTATUS Status = STATUS_SUCCESS;

    ULONG    Layer = 0;
    ULONG    Extra = 0;
    ULONG    Hint = 0;
    ULONG    Slot = 0;
    ULONG    Base = 0;

    Extra = End - Start;

	/* exceeds the biggest file size (indirect) */
    if (End > Vcb->max_data_blocks) {
        return STATUS_INVALID_PARAMETER;
    }

    for (Layer = 0; Layer < EXT2_BLOCK_TYPES && Extra; Layer++) {

        if (Start >= Vcb->max_blocks_per_layer[Layer]) {

            Base  += Vcb->max_blocks_per_layer[Layer];
            Start -= Vcb->max_blocks_per_layer[Layer];

        } else {

            /* get the slot in i_block array */
            if (Layer == 0) {
                Base = Slot = Start;
            } else {
                Slot = Layer + EXT2_NDIR_BLOCKS - 1;
            }

            /* set block hint to avoid fragments */
            if (Hint == 0) {
                if (Mcb->Inode.i_block[Slot] != 0) {
                    Hint = Mcb->Inode.i_block[Slot];
                } else if (Slot > 1) {
                    Hint = Mcb->Inode.i_block[Slot-1];
                }
            }

            /* now expand this slot */
            Status = Ext2ExpandBlock(
                         IrpContext,
                         Vcb,
                         Mcb,
                         Base,
                         Layer,
                         Start,
                         (Layer == 0) ? (Vcb->max_blocks_per_layer[Layer] - Start) : 1,
                         &Mcb->Inode.i_block[Slot],
                         &Hint,
                         &Extra
                     );
            if (!NT_SUCCESS(Status)) {
                break;
            }

            Start = 0;
            if (Layer == 0) {
                Base = 0;
            }
            Base += Vcb->max_blocks_per_layer[Layer];
        }
    }

    Size->QuadPart = ((LONGLONG)(End - Extra)) << BLOCK_BITS;

    /* save inode whatever it succeeds to expand or not */
    Ext2SaveInode(IrpContext, Vcb, &Mcb->Inode);

    return Status;
}

NTSTATUS
Ext2TruncateIndirectFast(
    PEXT2_IRP_CONTEXT IrpContext,
    PEXT2_VCB         Vcb,
    PEXT2_MCB         Mcb
    )
{
    LONGLONG            Vba;
    LONGLONG            Lba;
    LONGLONG            Length;
    NTSTATUS            Status = STATUS_SUCCESS;
    int                 i;

    /* try to load all indirect blocks if mcb zone is not initialized */
    if (!IsZoneInited(Mcb)) {
        Status = Ext2InitializeZone(IrpContext, Vcb, Mcb);
        if (!NT_SUCCESS(Status)) {
            DbgBreak();
            ClearLongFlag(Mcb->Flags, MCB_ZONE_INITED);
            goto errorout;
        }
    }

    ASSERT (IsZoneInited(Mcb));

    /* delete all data blocks here */
    if (FsRtlNumberOfRunsInLargeMcb(&Mcb->Extents) != 0) {
        for (i = 0; FsRtlGetNextLargeMcbEntry(&Mcb->Extents, i, &Vba, &Lba, &Length); i++) {
            /* ignore the non-existing runs */
            if (-1 == Lba || Vba == 0 || Length <= 0)
                continue;
            /* now do data block free */
            Ext2FreeBlock(IrpContext, Vcb, (ULONG)(Lba - 1), (ULONG)Length);
        }
    }

    /* delete all meta blocks here */
    if (FsRtlNumberOfRunsInLargeMcb(&Mcb->MetaExts) != 0) {
        for (i = 0; FsRtlGetNextLargeMcbEntry(&Mcb->MetaExts, i, &Vba, &Lba, &Length); i++) {
            /* ignore the non-existing runs */
            if (-1 == Lba || Vba == 0 || Length <= 0)
                continue;
            /* now do meta block free */
            Ext2FreeBlock(IrpContext, Vcb, (ULONG)(Lba - 1), (ULONG)Length);
        }
    }

    /* clear data and meta extents */
    Ext2ClearAllExtents(&Mcb->Extents);
    Ext2ClearAllExtents(&Mcb->MetaExts);
    ClearFlag(Mcb->Flags, MCB_ZONE_INITED);

    /* clear inode blocks & sizes */
    Mcb->Inode.i_blocks = 0;
    Mcb->Inode.i_size = 0;
    memset(&Mcb->Inode.i_block[0], 0, sizeof(__u32) * 15);

    /* the caller will do inode save */

errorout:

    return Status;
}

NTSTATUS
Ext2TruncateIndirect(
    PEXT2_IRP_CONTEXT IrpContext,
    PEXT2_VCB         Vcb,
    PEXT2_MCB         Mcb,
    PLARGE_INTEGER    Size
)
{
    NTSTATUS Status = STATUS_SUCCESS;

    ULONG    Layer = 0;

    ULONG    Extra = 0;
    ULONG    Wanted = 0;
    ULONG    End;
    ULONG    Base;

    ULONG    SizeArray = 0;
    PULONG   BlockArray = NULL;

    /* translate file size to block */
    End = Base = Vcb->max_data_blocks;
    Wanted = (ULONG)((Size->QuadPart + BLOCK_SIZE - 1) >> BLOCK_BITS);

    /* do fast deletion here */
    if (Wanted == 0) {
        Status = Ext2TruncateIndirectFast(IrpContext, Vcb, Mcb);
        if (NT_SUCCESS(Status))
            goto errorout;
    }

    /* calculate blocks to be freed */
    Extra = End - Wanted;

    for (Layer = EXT2_BLOCK_TYPES; Layer > 0 && Extra; Layer--) {

        if (Vcb->max_blocks_per_layer[Layer - 1] == 0) {
            continue;
        }

        Base -= Vcb->max_blocks_per_layer[Layer - 1];

        if (Layer - 1 == 0) {
            BlockArray = &Mcb->Inode.i_block[0];
            SizeArray = End;
            ASSERT(End == EXT2_NDIR_BLOCKS && Base == 0);
        } else {
            BlockArray = &Mcb->Inode.i_block[EXT2_NDIR_BLOCKS - 1 + Layer - 1];
            SizeArray = 1;
        }

        Status = Ext2TruncateBlock(
                     IrpContext,
                     Vcb,
                     Mcb,
                     Base,
                     End - Base - 1,
                     Layer - 1,
                     SizeArray,
                     BlockArray,
                     &Extra
                 );
        if (!NT_SUCCESS(Status)) {
            break;
        }

        End = Base;
    }

errorout:

    if (!NT_SUCCESS(Status)) {
        Size->QuadPart += ((ULONGLONG)Extra << BLOCK_BITS);
    }

    /* save inode */
    if (Mcb->Inode.i_size > (loff_t)(Size->QuadPart))
        Mcb->Inode.i_size = (loff_t)(Size->QuadPart);
    Ext2SaveInode(IrpContext, Vcb, &Mcb->Inode);

    return Status;
}
