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

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

#include "ext2fs.h"
#include "linux\ext4.h"

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

extern PEXT2_GLOBAL Ext2Global;

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

/* FUNCTIONS ***************************************************************/

NTSTATUS
Ext2LoadSuper(IN PEXT2_VCB      Vcb,
              IN BOOLEAN        bVerify,
              OUT PEXT2_SUPER_BLOCK * Sb)
{
    NTSTATUS          Status;
    PEXT2_SUPER_BLOCK Ext2Sb = NULL;

    Ext2Sb = (PEXT2_SUPER_BLOCK)
             Ext2AllocatePool(
                 PagedPool,
                 sizeof(EXT2_SUPER_BLOCK),
                 EXT2_SB_MAGIC
             );
    if (!Ext2Sb) {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto errorout;
    }

    Status = Ext2ReadDisk(
                 Vcb,
                 (ULONGLONG) SUPER_BLOCK_OFFSET,
                 sizeof(EXT2_SUPER_BLOCK),
                 (PVOID) Ext2Sb,
                 bVerify );

    if (!NT_SUCCESS(Status)) {
        Ext2FreePool(Ext2Sb, EXT2_SB_MAGIC);
        Ext2Sb = NULL;
    }

errorout:

    *Sb = Ext2Sb;
    return Status;
}

BOOLEAN
Ext2SaveSuper(
    IN PEXT2_IRP_CONTEXT    IrpContext,
    IN PEXT2_VCB            Vcb
)
{
    LONGLONG    offset;
    BOOLEAN     rc;

    ext4_superblock_csum_set(&Vcb->sb);
    offset = (LONGLONG) SUPER_BLOCK_OFFSET;
    rc = Ext2SaveBuffer( IrpContext,
                         Vcb,
                         offset,
                         sizeof(EXT2_SUPER_BLOCK),
                         Vcb->SuperBlock
                       );

    if (IsFlagOn(Vcb->Flags, VCB_FLOPPY_DISK)) {
        Ext2StartFloppyFlushDpc(Vcb, NULL, NULL);
    }

    return rc;
}

BOOLEAN
Ext2RefreshSuper (
    IN PEXT2_IRP_CONTEXT    IrpContext,
    IN PEXT2_VCB            Vcb
)
{
    LONGLONG        offset;
    IO_STATUS_BLOCK iosb;

    offset = (LONGLONG) SUPER_BLOCK_OFFSET;
    if (!CcCopyRead(
                Vcb->Volume,
                (PLARGE_INTEGER)&offset,
                sizeof(EXT2_SUPER_BLOCK),
                TRUE,
                (PVOID)Vcb->SuperBlock,
                &iosb )) {
        return FALSE;
    }

    if (!NT_SUCCESS(iosb.Status)) {
        return FALSE;
    }

    /* reload root inode */
    if (Vcb->McbTree) {

        if (!Ext2LoadInode(Vcb, &Vcb->McbTree->Inode))
            return FALSE;

        /* initializeroot node */
        Vcb->McbTree->LastAccessTime = Ext2GetInodeTime(Vcb->McbTree->Inode.i_atime, Vcb->McbTree->Inode.i_atime_extra);
        Vcb->McbTree->LastWriteTime = Ext2GetInodeTime(Vcb->McbTree->Inode.i_mtime, Vcb->McbTree->Inode.i_mtime_extra);
        Vcb->McbTree->ChangeTime = Ext2GetInodeTime(Vcb->McbTree->Inode.i_ctime, Vcb->McbTree->Inode.i_ctime_extra);
        if (Vcb->McbTree->Inode.i_crtime)
            Vcb->McbTree->CreationTime = Ext2GetInodeTime(Vcb->McbTree->Inode.i_crtime, Vcb->McbTree->Inode.i_crtime_extra);
        else
            Vcb->McbTree->CreationTime = Ext2GetInodeTime(Vcb->McbTree->Inode.i_ctime, Vcb->McbTree->Inode.i_ctime_extra);
    }

    return TRUE;
}

VOID
Ext2DropGroupBH(IN PEXT2_VCB Vcb)
{
    struct ext4_sb_info *sbi = &Vcb->sbi;
    unsigned long i;

    if (NULL == Vcb->sbi.s_gd) {
        return;
    }

    for (i = 0; i < Vcb->sbi.s_gdb_count; i++) {
        if (Vcb->sbi.s_gd[i].bh) {
            fini_bh(&sbi->s_gd[i].bh);
            Vcb->sbi.s_gd[i].bh = NULL;
        }
    }
}

VOID
Ext2PutGroup(IN PEXT2_VCB Vcb)
{
    struct ext4_sb_info *sbi = &Vcb->sbi;
    unsigned long i;

    if (NULL == Vcb->sbi.s_gd) {
        return;
    }

    Ext2DropGroupBH(Vcb);

    kfree(Vcb->sbi.s_gd);
    Vcb->sbi.s_gd = NULL;

    ClearFlag(Vcb->Flags, VCB_GD_LOADED);
}

BOOLEAN
Ext2LoadGroupBH(IN PEXT2_VCB Vcb)
{
    struct super_block  *sb = &Vcb->sb;
    struct ext4_sb_info *sbi = &Vcb->sbi;
    unsigned long i;
    BOOLEAN rc = FALSE;

    __try {

        ExAcquireResourceExclusiveLite(&Vcb->sbi.s_gd_lock, TRUE);
        ASSERT (NULL != sbi->s_gd);

        for (i = 0; i < sbi->s_gdb_count; i++) {
            ASSERT (sbi->s_gd[i].block);
            if (sbi->s_gd[i].bh)
                continue;
            sbi->s_gd[i].bh = sb_getblk(sb, sbi->s_gd[i].block);
            if (!sbi->s_gd[i].bh) {
                DEBUG(DL_ERR, ("Ext2LoadGroupBH: can't read group descriptor %d\n", i));
                DbgBreak();
                __leave;
            }
            sbi->s_gd[i].gd = (struct ext4_group_desc *)sbi->s_gd[i].bh->b_data;
        }

        rc = TRUE;

    } __finally {

        ExReleaseResourceLite(&Vcb->sbi.s_gd_lock);
    }

    return rc;
}

BOOLEAN
Ext2LoadGroup(IN PEXT2_VCB Vcb)
{
    struct super_block  *sb = &Vcb->sb;
    struct ext4_sb_info *sbi = &Vcb->sbi;
    ext3_fsblk_t sb_block = 1;
    unsigned long i;
    BOOLEAN rc = FALSE;

    __try {

        ExAcquireResourceExclusiveLite(&Vcb->sbi.s_gd_lock, TRUE);

        if (NULL == sbi->s_gd) {
            sbi->s_gd = kzalloc(sbi->s_gdb_count * sizeof(struct ext3_gd),
                                        GFP_KERNEL);
        }
        if (sbi->s_gd == NULL) {
            DEBUG(DL_ERR, ("Ext2LoadGroup: not enough memory.\n"));
            __leave;
        }

        if (BLOCK_SIZE != EXT3_MIN_BLOCK_SIZE) {
            sb_block = EXT4_MIN_BLOCK_SIZE / BLOCK_SIZE;
        }

        for (i = 0; i < sbi->s_gdb_count; i++) {
            sbi->s_gd[i].block =  descriptor_loc(sb, sb_block, i);
            if (!sbi->s_gd[i].block) {
                DEBUG(DL_ERR, ("Ext2LoadGroup: can't locate group descriptor %d\n", i));
                __leave;
            }
        }

        if (!Ext2LoadGroupBH(Vcb)) {
            DEBUG(DL_ERR, ("Ext2LoadGroup: Failed to load group descriptions !\n"));
            __leave;
        }

        if (!ext4_check_descriptors(sb)) {
            DbgBreak();
            DEBUG(DL_ERR, ("Ext2LoadGroup: group descriptors corrupted !\n"));
            __leave;
        }

        SetFlag(Vcb->Flags, VCB_GD_LOADED);
        rc = TRUE;

    } __finally {

        if (!rc)
            Ext2PutGroup(Vcb);

        ExReleaseResourceLite(&Vcb->sbi.s_gd_lock);
    }

    return rc;
}

VOID
Ext2DropBH(IN PEXT2_VCB Vcb)
{
    struct ext4_sb_info *sbi = &Vcb->sbi;

    /* do nothing if Vcb is not initialized yet */
    if (!IsFlagOn(Vcb->Flags, VCB_INITIALIZED))
        return;

    __try {

        /* acquire bd lock to avoid bh creation */
        ExAcquireResourceExclusiveLite(&Vcb->bd.bd_bh_lock, TRUE);

        SetFlag(Vcb->Flags, VCB_BEING_DROPPED);
        Ext2DropGroupBH(Vcb);

        while (!IsListEmpty(&Vcb->bd.bd_bh_free)) {
            struct buffer_head *bh;
            PLIST_ENTRY         l;
            l = RemoveHeadList(&Vcb->bd.bd_bh_free);
            bh = CONTAINING_RECORD(l, struct buffer_head, b_link);
            InitializeListHead(&bh->b_link);
            if (0 == atomic_read(&bh->b_count)) {
                buffer_head_remove(&Vcb->bd, bh);
                free_buffer_head(bh);
            }
        }

    } __finally {
        ExReleaseResourceLite(&Vcb->bd.bd_bh_lock);
    }

    ClearFlag(Vcb->Flags, VCB_BEING_DROPPED);
}

VOID
Ext2FlushRange(IN PEXT2_VCB Vcb, LARGE_INTEGER s, LARGE_INTEGER e)
{
    ULONG len;

    if (e.QuadPart <= s.QuadPart)
        return;

    /* loop per 2G */
    while (s.QuadPart < e.QuadPart) {
        if (e.QuadPart > s.QuadPart + 1024 * 1024 * 1024) {
            len = 1024 * 1024 * 1024;
        } else {
            len = (ULONG) (e.QuadPart - s.QuadPart);
        }
        CcFlushCache(&Vcb->SectionObject, &s, len, NULL);
        s.QuadPart += len;
    }
}

NTSTATUS
Ext2FlushVcb(IN PEXT2_VCB Vcb)
{
    LARGE_INTEGER        s = {0}, o;
    struct ext4_sb_info *sbi = &Vcb->sbi;
    struct rb_node      *node;
    struct buffer_head  *bh;

    if (!IsFlagOn(Vcb->Flags, VCB_GD_LOADED)) {
        CcFlushCache(&Vcb->SectionObject, NULL, 0, NULL);
        goto errorout;
    }

    ASSERT(ExIsResourceAcquiredExclusiveLite(&Vcb->MainResource));

    __try {

        /* acqurie gd block */
        ExAcquireResourceExclusiveLite(&Vcb->sbi.s_gd_lock, TRUE);

        /* acquire bd lock to avoid bh creation */
        ExAcquireResourceExclusiveLite(&Vcb->bd.bd_bh_lock, TRUE);

        /* drop unused bh */
        Ext2DropBH(Vcb);

        /* flush volume with all outstanding bh skipped */

        node = rb_first(&Vcb->bd.bd_bh_root);
        while (node) {

            bh = container_of(node, struct buffer_head, b_rb_node);
            node = rb_next(node);

            o.QuadPart = bh->b_blocknr << BLOCK_BITS;
            ASSERT(o.QuadPart >= s.QuadPart);

            if (o.QuadPart == s.QuadPart) {
                s.QuadPart = s.QuadPart + bh->b_size;
                continue;
            }

            if (o.QuadPart > s.QuadPart) {
                Ext2FlushRange(Vcb, s, o);
                s.QuadPart = (bh->b_blocknr << BLOCK_BITS) + bh->b_size;
                continue;
            }
        }

        o = Vcb->PartitionInformation.PartitionLength;
        Ext2FlushRange(Vcb, s, o);

    } __finally {

        ExReleaseResourceLite(&Vcb->bd.bd_bh_lock);
        ExReleaseResourceLite(&Vcb->sbi.s_gd_lock);
    }

errorout:
    return STATUS_SUCCESS;
}

BOOLEAN
Ext2SaveGroup(
    IN PEXT2_IRP_CONTEXT    IrpContext,
    IN PEXT2_VCB            Vcb,
    IN ULONG                Group
)
{
    struct ext4_group_desc *gd;
    struct buffer_head     *gb = NULL;
    unsigned long i;

    gd = ext4_get_group_desc(&Vcb->sb, Group, &gb);
    if (!gd)
        return 0;

    ext4_group_desc_csum_set(&Vcb->sb, Group, gd);
    mark_buffer_dirty(gb);
    fini_bh(&gb);

    return TRUE;
}

BOOLEAN
Ext2RefreshGroup(
    IN PEXT2_IRP_CONTEXT    IrpContext,
    IN PEXT2_VCB            Vcb
)
{
    return TRUE;
}

BOOLEAN
Ext2GetInodeLba (
    IN PEXT2_VCB    Vcb,
    IN  ULONG       inode,
    OUT PLONGLONG   offset
)
{
    PEXT2_GROUP_DESC gd;
    struct buffer_head *bh = NULL;
    ext4_fsblk_t loc;
    int group;

    if (inode < 1 || inode > INODES_COUNT)  {
        DEBUG(DL_ERR, ( "Ext2GetInodeLba: Inode value %xh is invalid.\n",inode));
        *offset = 0;
        return FALSE;
    }

    group = (inode - 1) / INODES_PER_GROUP ;
    gd = ext4_get_group_desc(&Vcb->sb, group, &bh);
    if (!bh) {
        *offset = 0;
        DbgBreak();
        return FALSE;
    }
    loc = (LONGLONG)ext4_inode_table(&Vcb->sb, gd);
    loc = loc << BLOCK_BITS;
    loc = loc + ((inode - 1) % INODES_PER_GROUP) * Vcb->InodeSize;

    *offset = loc;
    __brelse(bh);

    return TRUE;
}

void Ext2DecodeInode(struct inode *dst, struct ext4_inode *src)
{
    dst->i_mode = src->i_mode;
    dst->i_flags = src->i_flags;
    dst->i_uid = src->i_uid;
    dst->i_gid = src->i_gid;
    dst->i_nlink = src->i_links_count;
    dst->i_generation = src->i_generation;
    dst->i_size = src->i_size_lo;
    if (S_ISREG(src->i_mode)) {
        dst->i_size |= (loff_t)src->i_size_high << 32;
    }
    dst->i_file_acl = src->i_file_acl_lo;
    dst->i_file_acl |= (ext4_fsblk_t)src->osd2.linux2.l_i_file_acl_high << 32;
    dst->i_atime = src->i_atime;
    dst->i_ctime = src->i_ctime;
    dst->i_mtime = src->i_mtime;
    dst->i_dtime = src->i_dtime;
    dst->i_blocks = ext3_inode_blocks(src, dst);
    memcpy(&dst->i_block[0], &src->i_block[0], sizeof(__u32) * 15);
    if (EXT4_HAS_RO_COMPAT_FEATURE(dst->i_sb,
                                   EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE))
        dst->i_extra_isize = src->i_extra_isize;
    else
        dst->i_extra_isize = 0;
    if (EXT4_INODE_SIZE(dst->i_sb) > EXT4_GOOD_OLD_INODE_SIZE &&
        offsetof(struct ext4_inode, i_atime_extra) + sizeof(src->i_atime_extra) <= (ULONGLONG)EXT4_GOOD_OLD_INODE_SIZE + src->i_extra_isize) {
        dst->i_atime_extra = src->i_atime_extra;
        dst->i_ctime_extra = src->i_ctime_extra;
        dst->i_mtime_extra = src->i_mtime_extra;
    }
    if (EXT4_INODE_SIZE(dst->i_sb) > EXT4_GOOD_OLD_INODE_SIZE &&
        offsetof(struct ext4_inode, i_crtime_extra) + sizeof(src->i_crtime_extra) <= (ULONGLONG)EXT4_GOOD_OLD_INODE_SIZE + src->i_extra_isize) {
        dst->i_crtime = src->i_crtime;
        dst->i_crtime_extra = src->i_crtime_extra;
    }
}

void Ext2EncodeInode(struct ext4_inode *dst,  struct inode *src)
{
    dst->i_mode = src->i_mode;
    dst->i_flags = src->i_flags;
    dst->i_uid = src->i_uid;
    dst->i_gid = src->i_gid;
    dst->i_links_count = src->i_nlink;
    dst->i_generation = src->i_generation;
    dst->i_size_lo = (__u32)src->i_size;
    if (S_ISREG(src->i_mode)) {
        dst->i_size_high = (__u32)(src->i_size >> 32);
    }
    dst->i_file_acl_lo = (__u32)src->i_file_acl;
    dst->osd2.linux2.l_i_file_acl_high |= (__u16)(src->i_file_acl >> 32);
    dst->i_atime = src->i_atime;
    dst->i_ctime = src->i_ctime;
    dst->i_mtime = src->i_mtime;
    dst->i_dtime = src->i_dtime;
    ASSERT(src->i_sb);
    ext3_inode_blocks_set(dst, src);
    memcpy(&dst->i_block[0], &src->i_block[0], sizeof(__u32) * 15);
    if (EXT4_HAS_RO_COMPAT_FEATURE(src->i_sb,
                                   EXT4_FEATURE_RO_COMPAT_EXTRA_ISIZE))
        dst->i_extra_isize = src->i_extra_isize;
    if (EXT4_INODE_SIZE(src->i_sb) > EXT4_GOOD_OLD_INODE_SIZE &&
        offsetof(struct ext4_inode, i_atime_extra) + sizeof(dst->i_atime_extra) <= (ULONGLONG)EXT4_GOOD_OLD_INODE_SIZE + dst->i_extra_isize) {
        dst->i_atime_extra = src->i_atime_extra;
        dst->i_ctime_extra = src->i_ctime_extra;
        dst->i_mtime_extra = src->i_mtime_extra;
    }
    if (EXT4_INODE_SIZE(src->i_sb) > EXT4_GOOD_OLD_INODE_SIZE &&
        offsetof(struct ext4_inode, i_crtime_extra) + sizeof(dst->i_crtime_extra) <= (ULONGLONG)EXT4_GOOD_OLD_INODE_SIZE + dst->i_extra_isize) {
        dst->i_crtime = src->i_crtime;
        dst->i_crtime_extra = src->i_crtime_extra;
    }
}

BOOLEAN
Ext2LoadInode (IN PEXT2_VCB Vcb,
               IN struct inode *Inode)
{
    struct ext4_inode*      ext4i;
    struct ext4_inode_info  unused = {0};
    LONGLONG                offset;

    if (!Ext2GetInodeLba(Vcb, Inode->i_ino, &offset))  {
        DEBUG(DL_ERR, ("Ext2LoadInode: failed inode %u.\n", Inode->i_ino));
        return FALSE;
    }

    ext4i = (struct ext4_inode*) Ext2AllocatePool(NonPagedPool, EXT4_INODE_SIZE(Inode->i_sb), EXT2_INODE_MAGIC);
    if (!ext4i) {
        return FALSE;
    }

    if (!Ext2LoadBuffer(NULL, Vcb, offset, EXT4_INODE_SIZE(Inode->i_sb), ext4i)) {
        Ext2FreePool(ext4i, EXT2_INODE_MAGIC);
        return FALSE;
    }

    Ext2DecodeInode(Inode, ext4i);

    ext4_inode_csum_verify(Inode, ext4i, &unused);

    Ext2FreePool(ext4i, EXT2_INODE_MAGIC);

    return TRUE;
}

BOOLEAN
Ext2ClearInode (
    IN PEXT2_IRP_CONTEXT IrpContext,
    IN PEXT2_VCB Vcb,
    IN ULONG Inode)
{
    LONGLONG            Offset = 0;
    BOOLEAN             rc;

    rc = Ext2GetInodeLba(Vcb, Inode, &Offset);
    if (!rc)  {
        DEBUG(DL_ERR, ( "Ext2SaveInode: failed inode %u.\n", Inode));
        goto errorout;
    }

    rc = Ext2ZeroBuffer(IrpContext, Vcb, Offset, Vcb->InodeSize);

errorout:

    return rc;
}

BOOLEAN
Ext2SaveInode ( IN PEXT2_IRP_CONTEXT IrpContext,
                IN PEXT2_VCB Vcb,
                IN struct inode *Inode)
{
    struct ext4_inode*      ext4i;
    struct ext4_inode_info  unused = {0};
    LONGLONG                offset;
    BOOLEAN                 rc = 0;

    DEBUG(DL_INF, ( "Ext2SaveInode: Saving Inode %xh: Mode=%xh Size=%xh\n",
                    Inode->i_ino, Inode->i_mode, Inode->i_size));
    rc = Ext2GetInodeLba(Vcb,  Inode->i_ino, &offset);
    if (!rc)  {
        DEBUG(DL_ERR, ( "Ext2SaveInode: failed inode %u.\n", Inode->i_ino));
        goto errorout;
    }

    ext4i = (struct ext4_inode*) Ext2AllocatePool(NonPagedPool, EXT4_INODE_SIZE(Inode->i_sb), EXT2_INODE_MAGIC);
    if (!ext4i) {
        rc = FALSE;
        goto errorout;
    }

    rc = Ext2LoadBuffer(NULL, Vcb, offset, EXT4_INODE_SIZE(Inode->i_sb), ext4i);
    if (!rc) {
        DEBUG(DL_ERR, ( "Ext2SaveInode: failed reading inode %u.\n", Inode->i_ino));
        Ext2FreePool(ext4i, EXT2_INODE_MAGIC);
        goto errorout;
    }

    Ext2EncodeInode(ext4i, Inode);

    ext4_inode_csum_set(Inode, ext4i, &unused);

    rc = Ext2SaveBuffer(IrpContext, Vcb, offset, EXT4_INODE_SIZE(Inode->i_sb), ext4i);

    if (rc && IsFlagOn(Vcb->Flags, VCB_FLOPPY_DISK)) {
        Ext2StartFloppyFlushDpc(Vcb, NULL, NULL);
    }

    Ext2FreePool(ext4i, EXT2_INODE_MAGIC);

errorout:
    return rc;
}

BOOLEAN
Ext2LoadInodeXattr(IN PEXT2_VCB Vcb,
	IN struct inode *Inode,
	IN PEXT2_INODE InodeXattr)
{
	IO_STATUS_BLOCK     IoStatus;
	LONGLONG            Offset;

	if (!Ext2GetInodeLba(Vcb, Inode->i_ino, &Offset)) {
		DEBUG(DL_ERR, ("Ext2LoadRawInode: error get inode(%xh)'s addr.\n", Inode->i_ino));
		return FALSE;
	}

	if (!CcCopyRead(
		Vcb->Volume,
		(PLARGE_INTEGER)&Offset,
		Vcb->InodeSize,
		TRUE,
		(PVOID)InodeXattr,
		&IoStatus)) {
		return FALSE;
	}

	if (!NT_SUCCESS(IoStatus.Status)) {
		return FALSE;
	}

	Ext2EncodeInode(InodeXattr, Inode);
	return TRUE;
}

BOOLEAN
Ext2SaveInodeXattr(IN PEXT2_IRP_CONTEXT IrpContext,
	IN PEXT2_VCB Vcb,
	IN struct inode *Inode,
	IN PEXT2_INODE InodeXattr)
{
	IO_STATUS_BLOCK     IoStatus;
	LONGLONG            Offset = 0;
	ULONG               InodeSize = Vcb->InodeSize;
	BOOLEAN             rc = 0;

	/* There is no way to put EA information in such a small inode */
	if (InodeSize == EXT2_GOOD_OLD_INODE_SIZE)
		return FALSE;

	DEBUG(DL_INF, ("Ext2SaveInodeXattr: Saving Inode %xh: Mode=%xh Size=%xh\n",
		Inode->i_ino, Inode->i_mode, Inode->i_size));
	rc = Ext2GetInodeLba(Vcb, Inode->i_ino, &Offset);
	if (!rc) {
		DEBUG(DL_ERR, ("Ext2SaveInodeXattr: error get inode(%xh)'s addr.\n", Inode->i_ino));
		goto errorout;
	}

	rc = Ext2SaveBuffer(IrpContext,
									Vcb,
									Offset + EXT2_GOOD_OLD_INODE_SIZE + Inode->i_extra_isize,
									InodeSize - EXT2_GOOD_OLD_INODE_SIZE - Inode->i_extra_isize,
									(char *)InodeXattr + EXT2_GOOD_OLD_INODE_SIZE + Inode->i_extra_isize);

	if (rc && IsFlagOn(Vcb->Flags, VCB_FLOPPY_DISK)) {
		Ext2StartFloppyFlushDpc(Vcb, NULL, NULL);
	}

errorout:
	return rc;
}

BOOLEAN
Ext2LoadBlock (IN PEXT2_VCB Vcb,
               IN ULONG     Index,
               IN PVOID     Buffer )
{
    struct buffer_head *bh = NULL;
    BOOLEAN             rc = 0;

    __try {

        bh = sb_getblk(&Vcb->sb, (sector_t)Index);

        if (!bh) {
            DEBUG(DL_ERR, ("Ext2Loadblock: can't load block %u\n", Index));
            DbgBreak();
            __leave;
        }

        if (!buffer_uptodate(bh)) {
            int err = bh_submit_read(bh);
	        if (err < 0) {
	            DEBUG(DL_ERR, ("Ext2LoadBlock: reading failed %d\n", err));
		        __leave;
	        }
        }

        RtlCopyMemory(Buffer, bh->b_data, BLOCK_SIZE);
        rc = TRUE;

    } __finally {

        if (bh)
            fini_bh(&bh);
    }

    return rc; 
}

BOOLEAN
Ext2SaveBlock ( IN PEXT2_IRP_CONTEXT    IrpContext,
                IN PEXT2_VCB            Vcb,
                IN ULONG                Index,
                IN PVOID                Buf )
{
    struct buffer_head *bh = NULL;
    BOOLEAN             rc = 0;

    __try {

        bh = sb_getblk_zero(&Vcb->sb, (sector_t)Index);

        if (!bh) {
            DEBUG(DL_ERR, ("Ext2Saveblock: can't load block %u\n", Index));
            DbgBreak();
            __leave;
        }

        if (!buffer_uptodate(bh)) {
        }

        RtlCopyMemory(bh->b_data, Buf, BLOCK_SIZE);
        mark_buffer_dirty(bh);
        rc = TRUE;

    } __finally {

        if (bh)
            fini_bh(&bh);
    }

    return rc;
}

BOOLEAN
Ext2LoadBuffer( IN PEXT2_IRP_CONTEXT    IrpContext,
                IN PEXT2_VCB            Vcb,
                IN LONGLONG             offset,
                IN ULONG                size,
                IN PVOID                buf )
{
    struct buffer_head *bh = NULL;
    BOOLEAN             rc;

    __try {

        while (size) {

            sector_t    block;
            ULONG       len = 0, delta = 0;

            block = (sector_t) (offset >> BLOCK_BITS);
            delta = (ULONG)offset & (BLOCK_SIZE - 1);
            len = BLOCK_SIZE - delta;
            if (size < len)
                len = size;

            bh = sb_getblk(&Vcb->sb, block);
            if (!bh) {
                DEBUG(DL_ERR, ("Ext2SaveBuffer: can't load block %I64u\n", block));
                DbgBreak();
                __leave;
            }

            if (!buffer_uptodate(bh)) {
	            int err = bh_submit_read(bh);
	            if (err < 0) {
		            DEBUG(DL_ERR, ("Ext2SaveBuffer: bh_submit_read failed: %d\n", err));
		            __leave;
	            }
            }

            __try {
                RtlCopyMemory(buf, bh->b_data + delta, len);
            } __finally {
                fini_bh(&bh);
            }

            buf = (PUCHAR)buf + len;
            offset = offset + len;
            size = size - len;
        }

        rc = TRUE;

    } __finally {

        if (bh)
            fini_bh(&bh);

    }

    return rc;
}

BOOLEAN
Ext2ZeroBuffer( IN PEXT2_IRP_CONTEXT    IrpContext,
                IN PEXT2_VCB            Vcb,
                IN LONGLONG             offset,
                IN ULONG                size
    )
{
    struct buffer_head *bh = NULL;
    BOOLEAN             rc = 0;

    __try {

        while (size) {

            sector_t    block;
            ULONG       len = 0, delta = 0;

            block = (sector_t) (offset >> BLOCK_BITS);
            delta = (ULONG)offset & (BLOCK_SIZE - 1);
            len = BLOCK_SIZE - delta;
            if (size < len)
                len = size;

            if (delta == 0 && len >= BLOCK_SIZE) {
                bh = sb_getblk_zero(&Vcb->sb, block);
            } else {
                bh = sb_getblk(&Vcb->sb, block);
            }

            if (!bh) {
                DEBUG(DL_ERR, ("Ext2SaveBuffer: can't load block %I64u\n", block));
                DbgBreak();
                __leave;
            }

            if (!buffer_uptodate(bh)) {
	            int err = bh_submit_read(bh);
	            if (err < 0) {
		            DEBUG(DL_ERR, ("Ext2SaveBuffer: bh_submit_read failed: %d\n", err));
		            __leave;
	            }
            }

            __try {
                if (delta == 0 && len >= BLOCK_SIZE) {
                    /* bh (cache) was already cleaned as zero */
                } else {
                    RtlZeroMemory(bh->b_data + delta, len);
                }
                mark_buffer_dirty(bh);
            } __finally {
                fini_bh(&bh);
            }

            offset = offset + len;
            size = size - len;
        }

        rc = TRUE;

    } __finally {

        if (bh)
            fini_bh(&bh);

    }

    return rc;
}

BOOLEAN
Ext2SaveBuffer( IN PEXT2_IRP_CONTEXT    IrpContext,
                IN PEXT2_VCB            Vcb,
                IN LONGLONG             offset,
                IN ULONG                size,
                IN PVOID                buf )
{
    struct buffer_head *bh = NULL;
    BOOLEAN             rc = 0;

    __try {

        while (size) {

            sector_t    block;
            ULONG       len = 0, delta = 0;

            block = (sector_t) (offset >> BLOCK_BITS);
            delta = (ULONG)offset & (BLOCK_SIZE - 1);
            len = BLOCK_SIZE - delta;
            if (size < len)
                len = size;

            if (delta == 0 && len >= BLOCK_SIZE) {
                bh = sb_getblk_zero(&Vcb->sb, block);
            } else {
                bh = sb_getblk(&Vcb->sb, block);
            }

            if (!bh) {
                DEBUG(DL_ERR, ("Ext2SaveBuffer: can't load block %I64u\n", block));
                DbgBreak();
                __leave;
            }

            if (!buffer_uptodate(bh)) {
	            int err = bh_submit_read(bh);
	            if (err < 0) {
		            DEBUG(DL_ERR, ("Ext2SaveBuffer: bh_submit_read failed: %d\n", err));
		            __leave;
	            }
            }

            __try {
                RtlCopyMemory(bh->b_data + delta, buf, len);
                mark_buffer_dirty(bh);
            } __finally {
                fini_bh(&bh);
            }

            buf = (PUCHAR)buf + len;
            offset = offset + len;
            size = size - len;
        }

        rc = TRUE;

    } __finally {

        if (bh)
            fini_bh(&bh);

    }

    return rc;
}

VOID
Ext2UpdateVcbStat(
    IN PEXT2_IRP_CONTEXT    IrpContext,
    IN PEXT2_VCB            Vcb
)
{
    Vcb->SuperBlock->s_free_inodes_count = ext4_count_free_inodes(&Vcb->sb);
    ext3_free_blocks_count_set(SUPER_BLOCK, ext4_count_free_blocks(&Vcb->sb));
    Ext2SaveSuper(IrpContext, Vcb);
}

NTSTATUS
Ext2NewBlock(
    IN PEXT2_IRP_CONTEXT    IrpContext,
    IN PEXT2_VCB            Vcb,
    IN ULONG                GroupHint,
    IN ULONG                BlockHint,
    OUT PULONG              Block,
    IN OUT PULONG           Number
)
{
    struct super_block      *sb = &Vcb->sb;
    PEXT2_GROUP_DESC        gd;
    struct buffer_head     *gb = NULL;
    struct buffer_head     *bh = NULL;
    ext4_fsblk_t            bitmap_blk;

    RTL_BITMAP              BlockBitmap;

    ULONG                   Group = 0;
    ULONG                   Index = 0xFFFFFFFF;
    ULONG                   dwHint = 0;
    ULONG                   Count = 0;
    ULONG                   Length = 0;

    NTSTATUS                Status = STATUS_DISK_FULL;

    *Block = 0;

    ExAcquireResourceExclusiveLite(&Vcb->MetaBlock, TRUE);

    /* validate the hint group and hint block */
    if (GroupHint >= Vcb->sbi.s_groups_count) {
        DbgBreak();
        GroupHint = Vcb->sbi.s_groups_count - 1;
    }

    if (BlockHint != 0) {
        GroupHint = (BlockHint - EXT2_FIRST_DATA_BLOCK) / BLOCKS_PER_GROUP;
        dwHint = (BlockHint - EXT2_FIRST_DATA_BLOCK) % BLOCKS_PER_GROUP;
    }

    Group = GroupHint;

Again:

    if (bh)
        fini_bh(&bh);

    if (gb)
        fini_bh(&gb);

    gd = ext4_get_group_desc(sb, Group, &gb);
    if (!gd) {
        DbgBreak();
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto errorout;
    }

    bitmap_blk = ext4_block_bitmap(sb, gd);

    if (gd->bg_flags & cpu_to_le16(EXT4_BG_BLOCK_UNINIT)) {
        bh = sb_getblk_zero(sb, bitmap_blk);
        if (!bh) {
            DbgBreak();
            Status = STATUS_INSUFFICIENT_RESOURCES;
            goto errorout;
        }
        ext4_group_desc_csum_set(&Vcb->sb, Group, gd);
        ext4_init_block_bitmap(sb, bh, Group, gd);
        set_buffer_uptodate(bh);
        gd->bg_flags &= cpu_to_le16(~EXT4_BG_BLOCK_UNINIT);
        ext4_block_bitmap_csum_set(sb, Group, gd, bh);
        Ext2SaveGroup(IrpContext, Vcb, Group);
    } else {
        bh = sb_getblk(sb, bitmap_blk);
        if (!bh) {
            DbgBreak();
            Status = STATUS_INSUFFICIENT_RESOURCES;
            goto errorout;
        }
    }

    if (!buffer_uptodate(bh)) {
	    int err = bh_submit_read(bh);
	    if (err < 0) {
		    DbgPrint("bh_submit_read error! err: %d\n", err);
		    Status = Ext2WinntError(err);
		    goto errorout;
	    }
    }

    if (ext4_free_blks_count(sb, gd)) {

        if (Group == Vcb->sbi.s_groups_count - 1) {

            Length = (ULONG)(TOTAL_BLOCKS % BLOCKS_PER_GROUP);

            /* s_blocks_count is integer multiple of s_blocks_per_group */
            if (Length == 0) {
                Length = BLOCKS_PER_GROUP;
            }
        } else {
            Length = BLOCKS_PER_GROUP;
        }

        /* initialize bitmap buffer */
        RtlInitializeBitMap(&BlockBitmap, (PULONG)bh->b_data, Length);

        /* try to find a clear bit range */
        Index = RtlFindClearBits(&BlockBitmap, *Number, dwHint);

        /* We could not get new block in the prefered group */
        if (Index == 0xFFFFFFFF) {

            /* search clear bits from the hint block */
            Count = RtlFindNextForwardRunClear(&BlockBitmap, dwHint, &Index);
            if (dwHint != 0 && Count == 0) {
                /* search clear bits from the very beginning */
                Count = RtlFindNextForwardRunClear(&BlockBitmap, 0, &Index);
            }

            if (Count == 0) {

                RtlZeroMemory(&BlockBitmap, sizeof(RTL_BITMAP));

                /* no blocks found: set bg_free_blocks_count to 0 */
                ext4_free_blks_set(sb, gd, 0);
                ext4_block_bitmap_csum_set(sb, Group, gd, bh);
                Ext2SaveGroup(IrpContext, Vcb, Group);

                /* will try next group */
                goto Again;

            } else {

                /* we got free blocks */
                if (Count <= *Number) {
                    *Number = Count;
                }
            }
        }

    } else {

        /* try next group */
        dwHint = 0;
        Group = (Group + 1) % Vcb->sbi.s_groups_count;
        if (Group != GroupHint) {
            goto Again;
        }

        Index = 0xFFFFFFFF;
    }

    if (Index < Length) {

        /* mark block bits as allocated */
        RtlSetBits(&BlockBitmap, Index, *Number);

        /* set block bitmap dirty in cache */
        mark_buffer_dirty(bh);

        /* update group description */
        ext4_free_blks_set(sb, gd, RtlNumberOfClearBits(&BlockBitmap));
        ext4_block_bitmap_csum_set(sb, Group, gd, bh);
        Ext2SaveGroup(IrpContext, Vcb, Group);

        /* update Vcb free blocks */
        Ext2UpdateVcbStat(IrpContext, Vcb);

        /* validate the new allocated block number */
        *Block = Index + EXT2_FIRST_DATA_BLOCK + Group * BLOCKS_PER_GROUP;
        if (*Block >= TOTAL_BLOCKS || *Block + *Number > TOTAL_BLOCKS) {
            DbgBreak();
            dwHint = 0;
            goto Again;
        }

        if (ext4_block_bitmap(sb, gd) == *Block ||
            ext4_inode_bitmap(sb, gd) == *Block ||
            ext4_inode_table(sb,  gd)  == *Block ) {
            DbgBreak();
            dwHint = 0;
            goto Again;
        }

        /* Always remove dirty MCB to prevent Volume's lazy writing.
           Metadata blocks will be re-added during modifications.*/
        if (Ext2RemoveBlockExtent(Vcb, NULL, *Block, *Number)) {
        } else {
            DbgBreak();
            Ext2RemoveBlockExtent(Vcb, NULL, *Block, *Number);
        }

        DEBUG(DL_INF, ("Ext2NewBlock:  Block %xh - %x allocated.\n",
                       *Block, *Block + *Number));
        Status = STATUS_SUCCESS;
    }

errorout:

    ExReleaseResourceLite(&Vcb->MetaBlock);

    if (bh)
        fini_bh(&bh);

    if (gb)
        fini_bh(&gb);

    return Status;
}

NTSTATUS
Ext2FreeBlock(
    IN PEXT2_IRP_CONTEXT    IrpContext,
    IN PEXT2_VCB            Vcb,
    IN ULONG                Block,
    IN ULONG                Number
)
{
    struct super_block     *sb = &Vcb->sb;
    PEXT2_GROUP_DESC        gd;
    struct buffer_head     *gb = NULL;
    struct buffer_head      bh;
    ext4_fsblk_t            bitmap_blk;

    RTL_BITMAP      BlockBitmap;
    LARGE_INTEGER   Offset;

    PBCB            BitmapBcb;
    PVOID           BitmapCache;

    ULONG           Group;
    ULONG           Index;
    ULONG           Length;
    ULONG           Count;

    NTSTATUS        Status = STATUS_UNSUCCESSFUL;

    ExAcquireResourceExclusiveLite(&Vcb->MetaBlock, TRUE);

    DEBUG(DL_INF, ("Ext2FreeBlock: Block %xh - %x to be freed.\n",
                   Block, Block + Number));

    Group = (Block - EXT2_FIRST_DATA_BLOCK) / BLOCKS_PER_GROUP;
    Index = (Block - EXT2_FIRST_DATA_BLOCK) % BLOCKS_PER_GROUP;

Again:

    if (gb)
        fini_bh(&gb);

    if ( Block < EXT2_FIRST_DATA_BLOCK ||
         Block >= TOTAL_BLOCKS ||
         Group >= Vcb->sbi.s_groups_count) {

        DbgBreak();
        Status = STATUS_SUCCESS;

    } else  {

        gd = ext4_get_group_desc(sb, Group, &gb);
        if (!gd) {
            DbgBreak();
            Status = STATUS_INSUFFICIENT_RESOURCES;
            goto errorout;
        }
        bitmap_blk = ext4_block_bitmap(sb, gd);

        /* check the block is valid or not */
        if (bitmap_blk >= TOTAL_BLOCKS) {
            DbgBreak();
            Status = STATUS_DISK_CORRUPT_ERROR;
            goto errorout;
        }

        /* get bitmap block offset and length */
        Offset.QuadPart = bitmap_blk;
        Offset.QuadPart = Offset.QuadPart << BLOCK_BITS;

        if (Group == Vcb->sbi.s_groups_count - 1) {

            Length = (ULONG)(TOTAL_BLOCKS % BLOCKS_PER_GROUP);

            /* s_blocks_count is integer multiple of s_blocks_per_group */
            if (Length == 0) {
                Length = BLOCKS_PER_GROUP;
            }

        } else {
            Length = BLOCKS_PER_GROUP;
        }

        /* read and initialize bitmap */
        if (!CcPinRead( Vcb->Volume,
                        &Offset,
                        Vcb->BlockSize,
                        PIN_WAIT,
                        &BitmapBcb,
                        &BitmapCache ) ) {

            DEBUG(DL_ERR, ("Ext2FreeBlock: failed to PinLock bitmap block %xh.\n",
                           bitmap_blk));
            Status = STATUS_CANT_WAIT;
            DbgBreak();
            goto errorout;
        }

        /* clear usused bits */
        RtlInitializeBitMap(&BlockBitmap, BitmapCache, Length);
        Count = min(Length - Index, Number);
        RtlClearBits(&BlockBitmap, Index, Count);

        /* update group description table */
        ext4_free_blks_set(sb, gd, RtlNumberOfClearBits(&BlockBitmap));

        bh.b_data = BitmapCache;
        ext4_block_bitmap_csum_set(sb, Group, gd, &bh);

        /* indict the cache range is dirty */
        CcSetDirtyPinnedData(BitmapBcb, NULL );
        Ext2AddVcbExtent(Vcb, Offset.QuadPart, (LONGLONG)Vcb->BlockSize);
        CcUnpinData(BitmapBcb);
        BitmapBcb = NULL;
        BitmapCache = NULL;
        Ext2SaveGroup(IrpContext, Vcb, Group);

        /* remove dirty MCB to prevent Volume's lazy writing. */
        if (Ext2RemoveBlockExtent(Vcb, NULL, Block, Count)) {
        } else {
            DbgBreak();
            Ext2RemoveBlockExtent(Vcb, NULL, Block, Count);
        }

        /* save super block (used/unused blocks statics) */
        Ext2UpdateVcbStat(IrpContext, Vcb);

        /* try next group to clear all remaining */
        Number -= Count;
        if (Number) {
            Group += 1;
            if (Group < Vcb->sbi.s_groups_count) {
                Index = 0;
                Block += Count;
                goto Again;
            } else {
                DEBUG(DL_ERR, ("Ext2FreeBlock: block number beyonds max group.\n"));
                goto errorout;
            }
        }
    }

    Status = STATUS_SUCCESS;

errorout:

    if (gb)
        fini_bh(&gb);

    ExReleaseResourceLite(&Vcb->MetaBlock);

    return Status;
}

NTSTATUS
Ext2NewInode(
    IN PEXT2_IRP_CONTEXT    IrpContext,
    IN PEXT2_VCB            Vcb,
    IN ULONG                GroupHint,
    IN ULONG                Type,
    OUT PULONG              Inode
)
{
    struct super_block     *sb = &Vcb->sb;
    PEXT2_GROUP_DESC        gd;
    struct buffer_head     *gb = NULL;
    struct buffer_head     *bh = NULL;
    ext4_fsblk_t            bitmap_blk;

    RTL_BITMAP      InodeBitmap;

    ULONG           Group, i, j;
    ULONG           Average, Length;

    ULONG           dwInode;

    NTSTATUS        Status = STATUS_DISK_FULL;

    *Inode = dwInode = 0XFFFFFFFF;

    ExAcquireResourceExclusiveLite(&Vcb->MetaInode, TRUE);

    if (GroupHint >= Vcb->sbi.s_groups_count)
        GroupHint = GroupHint % Vcb->sbi.s_groups_count;

repeat:

    if (bh)
        fini_bh(&bh);

    if (gb)
        fini_bh(&gb);

    Group = i = 0;
    gd = NULL;

    if (Type == EXT2_FT_DIR) {

        Average = Vcb->SuperBlock->s_free_inodes_count / Vcb->sbi.s_groups_count;

        for (j = 0; j < Vcb->sbi.s_groups_count; j++) {

            i = (j + GroupHint) % (Vcb->sbi.s_groups_count);
            gd = ext4_get_group_desc(sb, i, &gb);
            if (!gd) {
                    DbgBreak();
                Status = STATUS_INSUFFICIENT_RESOURCES;
                goto errorout;
            }

            if ((gd->bg_flags & cpu_to_le16(EXT4_BG_INODE_UNINIT)) ||
                (ext4_used_dirs_count(sb, gd) << 8 < 
                 ext4_free_inodes_count(sb, gd)) ) {
                Group = i + 1;
                break;
            }
            fini_bh(&gb);
        }

        if (!Group) {

            PEXT2_GROUP_DESC  desc = NULL;

            gd = NULL;

            /* get the group with the biggest vacancy */
            for (j = 0; j < Vcb->sbi.s_groups_count; j++) {

                struct buffer_head *gt = NULL;
                desc = ext4_get_group_desc(sb, j, &gt);
                if (!desc) {
                    DbgBreak();
                    Status = STATUS_INSUFFICIENT_RESOURCES;
                    goto errorout;
                }

                /* return the group if it's not initialized yet */
                if (desc->bg_flags & cpu_to_le16(EXT4_BG_INODE_UNINIT)) {
                    Group = j + 1;
                    gd = desc;

                    if (gb)
                        fini_bh(&gb);
                    gb = gt;
                    gt = NULL;
                    break;
                }

                if (!gd) {
                    if (ext4_free_inodes_count(sb, desc) > 0) {
                        Group = j + 1;
                        gd = desc;
                        if (gb)
                            fini_bh(&gb);
                        gb = gt;
                        gt = NULL;
                    }
                } else {
                    if (ext4_free_inodes_count(sb, desc) >
                        ext4_free_inodes_count(sb, gd)) {
                        Group = j + 1;
                        gd = desc;
                        if (gb)
                            fini_bh(&gb);
                        gb = gt;
                        gt = NULL;
                        break;
                    }
                }
                if (gt)
                    fini_bh(&gt);
            }
        }

    } else {

        /*
         * Try to place the inode in its parent directory (GroupHint)
         */

        gd = ext4_get_group_desc(sb, GroupHint, &gb);
        if (!gb) {
            DbgBreak();
            Status = STATUS_INSUFFICIENT_RESOURCES;
            goto errorout;
        }

        if (gd->bg_flags & cpu_to_le16(EXT4_BG_INODE_UNINIT) ||
            ext4_free_inodes_count(sb, gd)) {

            Group = GroupHint + 1;

        } else {

            /* this group is 100% cocucpied */
            fini_bh(&gb);
 
            i = GroupHint;

            /*
             * Use a quadratic hash to find a group with a free inode
             */

            for (j = 1; j < Vcb->sbi.s_groups_count; j <<= 1) {

                i = (i + j) % Vcb->sbi.s_groups_count;
                gd = ext4_get_group_desc(sb, i, &gb);
                if (!gd) {
                    DbgBreak();
                    Status = STATUS_INSUFFICIENT_RESOURCES;
                    goto errorout;
                }

                if (gd->bg_flags & cpu_to_le16(EXT4_BG_INODE_UNINIT) ||
                    ext4_free_inodes_count(sb, gd)) {
                    Group = i + 1;
                    break;
                }

                fini_bh(&gb);                
            }
        }

        if (!Group) {
            /*
             * That failed: try linear search for a free inode
             */
            i = GroupHint;
            for (j = 2; j < Vcb->sbi.s_groups_count; j++) {

                i = (i + 1) % Vcb->sbi.s_groups_count;
                gd = ext4_get_group_desc(sb, i, &gb);
                if (!gd) {
                    DbgBreak();
                    Status = STATUS_INSUFFICIENT_RESOURCES;
                    goto errorout;
                }

                if (gd->bg_flags & cpu_to_le16(EXT4_BG_INODE_UNINIT) ||
                    ext4_free_inodes_count(sb, gd)) {
                    Group = i + 1;
                    break;
                }

                fini_bh(&gb);
            }
        }
    }

    if (gd == NULL || Group == 0) {
        goto errorout;
    }

    /* finally we got the group, but is it valid ? */
    if (Group > Vcb->sbi.s_groups_count) {
        DbgBreak();
        goto errorout;
    }

    /* valid group number starts from 1, not 0 */
    Group -= 1;

    ASSERT(gd);
    bitmap_blk = ext4_inode_bitmap(sb, gd);
    /* check the block is valid or not */
    if (bitmap_blk == 0 || bitmap_blk >= TOTAL_BLOCKS) {
        DbgBreak();
        Status = STATUS_DISK_CORRUPT_ERROR;
        goto errorout;
    }

    if (gd->bg_flags & cpu_to_le16(EXT4_BG_INODE_UNINIT)) {
        bh = sb_getblk_zero(sb, bitmap_blk);
        if (!bh) {
            DbgBreak();
            Status = STATUS_INSUFFICIENT_RESOURCES;
            goto errorout;
        }
        ext4_group_desc_csum_set(&Vcb->sb, Group, gd);
        ext4_init_inode_bitmap(sb, bh, Group, gd);
        set_buffer_uptodate(bh);
        gd->bg_flags &= cpu_to_le16(~EXT4_BG_INODE_UNINIT);
        ext4_inode_bitmap_csum_set(sb, Group, gd, bh, EXT4_INODES_PER_GROUP(sb) / 8);
        Ext2SaveGroup(IrpContext, Vcb, Group);
    } else {
        bh = sb_getblk(sb, bitmap_blk);
        if (!bh) {
            DbgBreak();
            Status = STATUS_INSUFFICIENT_RESOURCES;
            goto errorout;
        }
    }

    if (!buffer_uptodate(bh)) {
	    int err = bh_submit_read(bh);
	    if (err < 0) {
		    DbgPrint("bh_submit_read error! err: %d\n", err);
		    Status = Ext2WinntError(err);
		    goto errorout;
	    }
    }

    if (Vcb->sbi.s_groups_count == 1) {
        Length = INODES_COUNT;
    } else {
        if (Group + 1 == Vcb->sbi.s_groups_count) {
            Length = INODES_COUNT % INODES_PER_GROUP;
            if (!Length) {
                /* INODES_COUNT is integer multiple of INODES_PER_GROUP */
                Length = INODES_PER_GROUP;
            }
        } else  {
            Length = INODES_PER_GROUP;
        }
    }

    RtlInitializeBitMap(&InodeBitmap, (PULONG)bh->b_data, Length);
    dwInode = RtlFindClearBits(&InodeBitmap, 1, 0);

    if (dwInode == 0xFFFFFFFF || dwInode >= Length) {

        RtlZeroMemory(&InodeBitmap, sizeof(RTL_BITMAP));
        if (ext4_free_inodes_count(sb, gd) > 0) {
            ext4_free_inodes_set(sb, gd, 0);
            ext4_inode_bitmap_csum_set(sb, Group, gd, bh, EXT4_INODES_PER_GROUP(sb) / 8);
            Ext2SaveGroup(IrpContext, Vcb, Group);
        }
        goto repeat;

    } else {

        __u32 count = 0;

        /* update unused inodes count */
        count = ext4_free_inodes_count(sb, gd) - 1;
        ext4_free_inodes_set(sb, gd, count);

        RtlSetBits(&InodeBitmap, dwInode, 1);

        /* set block bitmap dirty in cache */
        mark_buffer_dirty(bh);

        /* If we didn't allocate from within the initialized part of the inode
         * table then we need to initialize up to this inode. */
        if (EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_GDT_CSUM) ||
            EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) {

            __u32 free;

            if (gd->bg_flags & cpu_to_le16(EXT4_BG_INODE_UNINIT)) {
                gd->bg_flags &= cpu_to_le16(~EXT4_BG_INODE_UNINIT);
                /* When marking the block group with
                 * ~EXT4_BG_INODE_UNINIT we don't want to depend
                 * on the value of bg_itable_unused even though
                 * mke2fs could have initialized the same for us.
                 * Instead we calculated the value below
                 */

                free = 0;
            } else {
                free = EXT3_INODES_PER_GROUP(sb) - ext4_itable_unused_count(sb, gd);
            }

            /*
             * Check the relative inode number against the last used
             * relative inode number in this group. if it is greater
             * we need to  update the bg_itable_unused count
             *
             */
            if (dwInode + 1 > free) {
                ext4_itable_unused_set(sb, gd,
                                       (EXT3_INODES_PER_GROUP(sb) - 1 - dwInode));
            }

            /* We may have to initialize the block bitmap if it isn't already */
            if (gd->bg_flags & cpu_to_le16(EXT4_BG_BLOCK_UNINIT)) {

                struct buffer_head *block_bitmap_bh = NULL;

                /* recheck and clear flag under lock if we still need to */
                block_bitmap_bh = sb_getblk_zero(sb, ext4_block_bitmap(sb, gd));
                if (block_bitmap_bh) {
                    ext4_group_desc_csum_set(&Vcb->sb, Group, gd);
                    free = ext4_init_block_bitmap(sb, block_bitmap_bh, Group, gd);
                    ext4_block_bitmap_csum_set(sb, Group, gd, block_bitmap_bh);
                    set_buffer_uptodate(block_bitmap_bh);
                    brelse(block_bitmap_bh);
                    gd->bg_flags &= cpu_to_le16(~EXT4_BG_BLOCK_UNINIT);
                    ext4_free_blks_set(sb, gd, free);
                    Ext2SaveGroup(IrpContext, Vcb, Group);
                }
            }
        }

        *Inode = dwInode + 1 + Group * INODES_PER_GROUP;

        /* update group_desc / super_block */
        if (Type == EXT2_FT_DIR) {
            ext4_used_dirs_set(sb, gd, ext4_used_dirs_count(sb, gd) + 1);
        }
        ext4_inode_bitmap_csum_set(sb, Group, gd, bh, EXT4_INODES_PER_GROUP(sb) / 8);
        Ext2SaveGroup(IrpContext, Vcb, Group);
        Ext2UpdateVcbStat(IrpContext, Vcb);
        Status = STATUS_SUCCESS;
    }

errorout:

    ExReleaseResourceLite(&Vcb->MetaInode);

    if (bh)
        fini_bh(&bh);

    if (gb)
        fini_bh(&gb);

    return Status;
}

NTSTATUS
Ext2UpdateGroupDirStat(
    IN PEXT2_IRP_CONTEXT    IrpContext,
    IN PEXT2_VCB            Vcb,
    IN ULONG                group
    )
{
    struct super_block     *sb = &Vcb->sb;
    PEXT2_GROUP_DESC        gd;
    struct buffer_head     *gb = NULL;
    NTSTATUS                status;

    ExAcquireResourceExclusiveLite(&Vcb->MetaInode, TRUE);

    /* get group desc */
    gd = ext4_get_group_desc(sb, group, &gb);
    if (!gd) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto errorout;
    }

    /* update group_desc and super_block */
    ext4_used_dirs_set(sb, gd, ext4_used_dirs_count(sb, gd) - 1);
    Ext2SaveGroup(IrpContext, Vcb, group);
    Ext2UpdateVcbStat(IrpContext, Vcb);
    status = STATUS_SUCCESS;

errorout:

    ExReleaseResourceLite(&Vcb->MetaInode);

    if (gb)
        fini_bh(&gb);

    return status;
}

NTSTATUS
Ext2FreeInode(
    IN PEXT2_IRP_CONTEXT    IrpContext,
    IN PEXT2_VCB            Vcb,
    IN ULONG                Inode,
    IN ULONG                Type
)
{
    struct super_block     *sb = &Vcb->sb;
    PEXT2_GROUP_DESC        gd;
    struct buffer_head     *gb = NULL;
    struct buffer_head     *bh = NULL;
    ext4_fsblk_t            bitmap_blk;

    RTL_BITMAP      InodeBitmap;
    ULONG           Group;
    ULONG           Length;
    LARGE_INTEGER   Offset;

    ULONG           dwIno;
    BOOLEAN         bModified = FALSE;

    NTSTATUS        Status = STATUS_UNSUCCESSFUL;

    ExAcquireResourceExclusiveLite(&Vcb->MetaInode, TRUE);

    Group = (Inode - 1) / INODES_PER_GROUP;
    dwIno = (Inode - 1) % INODES_PER_GROUP;

    DEBUG(DL_INF, ( "Ext2FreeInode: Inode: %xh (Group/Off = %xh/%xh)\n",
                    Inode, Group, dwIno));

    if (Group >= Vcb->sbi.s_groups_count)  {
        DbgBreak();
        goto errorout;
    }

    gd = ext4_get_group_desc(sb, Group, &gb);
    if (!gd) {
        DbgBreak();
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto errorout;
    }

    bitmap_blk = ext4_inode_bitmap(sb, gd);
    bh = sb_getblk(sb, bitmap_blk);
    if (!bh) {
        DbgBreak();
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto errorout;
    }
    if (!buffer_uptodate(bh)) {
        int err = bh_submit_read(bh);
        if (err < 0) {
            DbgPrint("bh_submit_read error! err: %d\n", err);
            Status = Ext2WinntError(err);
            goto errorout;
        }
    }

    if (Group == Vcb->sbi.s_groups_count - 1) {

        Length = INODES_COUNT % INODES_PER_GROUP;
        if (!Length) {
            /* s_inodes_count is integer multiple of s_inodes_per_group */
            Length = INODES_PER_GROUP;
        }
    } else {
        Length = INODES_PER_GROUP;
    }

    RtlInitializeBitMap(&InodeBitmap, (PULONG)bh->b_data, Length);

    if (RtlCheckBit(&InodeBitmap, dwIno) == 0) {
        DbgBreak();
        Status = STATUS_SUCCESS;
    } else {
        RtlClearBits(&InodeBitmap, dwIno, 1);
        bModified = TRUE;
    }

    if (bModified) {
        /* update group free inodes */
        ext4_free_inodes_set(sb, gd,
                             RtlNumberOfClearBits(&InodeBitmap));

        /* set inode block dirty and add to vcb dirty range */
        mark_buffer_dirty(bh);

        /* update group_desc and super_block */
        if (Type == EXT2_FT_DIR) {
            ext4_used_dirs_set(sb, gd,
                               ext4_used_dirs_count(sb, gd) - 1);
        }
        ext4_inode_bitmap_csum_set(sb, Group, gd, bh, EXT4_INODES_PER_GROUP(sb) / 8);
        Ext2SaveGroup(IrpContext, Vcb, Group);
        Ext2UpdateVcbStat(IrpContext, Vcb);
        Status = STATUS_SUCCESS;
    }

errorout:

    ExReleaseResourceLite(&Vcb->MetaInode);

    if (bh)
        fini_bh(&bh);

    if (gb)
        fini_bh(&gb);

    return Status;
}

NTSTATUS
Ext2AddEntry (
    IN PEXT2_IRP_CONTEXT   IrpContext,
    IN PEXT2_VCB           Vcb,
    IN PEXT2_FCB           Dcb,
    IN struct inode       *Inode,
    IN PUNICODE_STRING     FileName,
    struct dentry        **Dentry
)
{
    struct dentry          *de = NULL;

    NTSTATUS                status = STATUS_UNSUCCESSFUL;
    OEM_STRING              oem;
    int                     rc;

    BOOLEAN                 MainResourceAcquired = FALSE;

    if (!IsDirectory(Dcb)) {
        DbgBreak();
        return STATUS_NOT_A_DIRECTORY;
    }

    ExAcquireResourceExclusiveLite(&Dcb->MainResource, TRUE);
    MainResourceAcquired = TRUE;

    __try {

        Ext2ReferXcb(&Dcb->ReferenceCount);
        de = Ext2BuildEntry(Vcb, Dcb->Mcb, FileName);
        if (!de) {
            status = STATUS_INSUFFICIENT_RESOURCES;
            __leave;
        }
        de->d_inode = Inode;

        rc = ext3_add_entry(IrpContext, de, Inode);
        status = Ext2WinntError(rc);
        if (NT_SUCCESS(status)) {

            /* increase dir inode's nlink for .. */
            if (S_ISDIR(Inode->i_mode)) {
                ext3_inc_count(Dcb->Inode);
                ext3_mark_inode_dirty(IrpContext, Dcb->Inode);
            }

            /* increase inode nlink reference */
            ext3_inc_count(Inode);
            ext3_mark_inode_dirty(IrpContext, Inode);

            if (Dentry) {
                *Dentry = de;
                de = NULL;
            }
        }

    } __finally {

        Ext2DerefXcb(&Dcb->ReferenceCount);

        if (MainResourceAcquired)    {
            ExReleaseResourceLite(&Dcb->MainResource);
        }

        if (de)
            Ext2FreeEntry(de);
    }

    return status;
}

NTSTATUS
Ext2SetFileType (
    IN PEXT2_IRP_CONTEXT    IrpContext,
    IN PEXT2_VCB            Vcb,
    IN PEXT2_FCB            Dcb,
    IN PEXT2_MCB            Mcb,
    IN umode_t              mode
    )
{
    struct inode *dir = Dcb->Inode;
    struct buffer_head *bh = NULL;
    struct ext3_dir_entry_2 *de;
    struct inode *inode;
    NTSTATUS Status = STATUS_UNSUCCESSFUL;
    BOOLEAN  MainResourceAcquired = FALSE;

    if (!EXT4_HAS_INCOMPAT_FEATURE(dir->i_sb, EXT3_FEATURE_INCOMPAT_FILETYPE)) {
        return STATUS_SUCCESS;
    }

    if (!IsDirectory(Dcb)) {
        return STATUS_NOT_A_DIRECTORY;
    }

    ExAcquireResourceExclusiveLite(&Dcb->MainResource, TRUE);
    MainResourceAcquired = TRUE;

    __try {

        Ext2ReferXcb(&Dcb->ReferenceCount);

        bh = ext3_find_entry(IrpContext, Mcb->de, &de);
        if (!bh)
            __leave;

        inode = &Mcb->Inode;
        if (le32_to_cpu(de->inode) != inode->i_ino)
            __leave;

        ext3_set_de_type(inode->i_sb, de, mode);
        mark_buffer_dirty(bh);

        if (S_ISDIR(inode->i_mode) == S_ISDIR(mode)) {
        } else if (S_ISDIR(inode->i_mode)) {
            ext3_dec_count(dir);
        } else if (S_ISDIR(mode)) {
            ext3_inc_count(dir);
        }
        dir->i_ctime = dir->i_mtime = ext3_current_time(dir);
        ext3_mark_inode_dirty(IrpContext, dir);

        inode->i_mode = mode;
        ext3_mark_inode_dirty(IrpContext, inode);

        Status = STATUS_SUCCESS;

    } __finally {

        Ext2DerefXcb(&Dcb->ReferenceCount);

        if (MainResourceAcquired)
            ExReleaseResourceLite(&Dcb->MainResource);

        if (bh)
            brelse(bh);
    }

    return Status;
}

NTSTATUS
Ext2RemoveEntry (
    IN PEXT2_IRP_CONTEXT    IrpContext,
    IN PEXT2_VCB            Vcb,
    IN PEXT2_FCB            Dcb,
    IN PEXT2_MCB            Mcb
)
{
    struct inode *dir = Dcb->Inode;
    struct buffer_head *bh = NULL;
    struct ext3_dir_entry_2 *de;
    struct inode *inode;
    int rc = -ENOENT;
    NTSTATUS Status = STATUS_UNSUCCESSFUL;
    BOOLEAN  MainResourceAcquired = FALSE;

    if (!IsDirectory(Dcb)) {
        return STATUS_NOT_A_DIRECTORY;
    }

    ExAcquireResourceExclusiveLite(&Dcb->MainResource, TRUE);
    MainResourceAcquired = TRUE;

    __try {

        Ext2ReferXcb(&Dcb->ReferenceCount);

        bh = ext3_find_entry(IrpContext, Mcb->de, &de);
        if (!bh)
            __leave;

        inode = &Mcb->Inode;
        if (le32_to_cpu(de->inode) != inode->i_ino)
            __leave;

        if (!inode->i_nlink) {
            ext3_warning (inode->i_sb, "ext3_unlink",
                          "Deleting nonexistent file (%lu), %d",
                          inode->i_ino, inode->i_nlink);
            inode->i_nlink = 1;
        }
        rc = ext3_delete_entry(IrpContext, dir, de, bh);
        if (rc) {
            Status = Ext2WinntError(rc);
            __leave;
        }
        /*
        	    if (!inode->i_nlink)
        		    ext3_orphan_add(handle, inode);
        */
        dir->i_ctime = dir->i_mtime = ext3_current_time(dir);
        inode->i_ctime = inode->i_mtime = ext3_current_time(inode);
        ext3_dec_count(inode);
        ext3_mark_inode_dirty(IrpContext, inode);

        /* decrease dir inode's nlink for .. */
        if (S_ISDIR(inode->i_mode)) {
            ext3_update_dx_flag(dir);
            ext3_dec_count(dir);
            ext3_mark_inode_dirty(IrpContext, dir);
        }

        Status = STATUS_SUCCESS;

    } __finally {

        Ext2DerefXcb(&Dcb->ReferenceCount);

        if (MainResourceAcquired)
            ExReleaseResourceLite(&Dcb->MainResource);

        if (bh)
            brelse(bh);
    }

    return Status;
}

NTSTATUS
Ext2SetParentEntry (
    IN PEXT2_IRP_CONTEXT   IrpContext,
    IN PEXT2_VCB           Vcb,
    IN PEXT2_FCB           Dcb,
    IN ULONG               OldParent,
    IN ULONG               NewParent )
{
    NTSTATUS                Status = STATUS_UNSUCCESSFUL;

    PEXT2_DIR_ENTRY2        pSelf   = NULL;
    PEXT2_DIR_ENTRY2        pParent = NULL;

    ULONG                   dwBytes = 0;

    BOOLEAN                 MainResourceAcquired = FALSE;

    ULONG                   Offset = 0;

    if (!IsDirectory(Dcb)) {
        return STATUS_NOT_A_DIRECTORY;
    }

    if (OldParent == NewParent) {
        return STATUS_SUCCESS;
    }

    MainResourceAcquired =
        ExAcquireResourceExclusiveLite(&Dcb->MainResource, TRUE);

    __try {

        Ext2ReferXcb(&Dcb->ReferenceCount);

        pSelf = (PEXT2_DIR_ENTRY2)
                Ext2AllocatePool(
                    PagedPool,
                    EXT2_DIR_REC_LEN(1) + EXT2_DIR_REC_LEN(2),
                    EXT2_DENTRY_MAGIC
                );
        if (!pSelf) {
            DEBUG(DL_ERR, ( "Ex2SetParentEntry: failed to allocate pSelf.\n"));
            Status = STATUS_INSUFFICIENT_RESOURCES;
            __leave;
        }

        dwBytes = 0;

        //
        // Reading the DCB contents
        //

        Status = Ext2ReadInode(
                     IrpContext,
                     Vcb,
                     Dcb->Mcb,
                     (ULONGLONG)Offset,
                     (PVOID)pSelf,
                     EXT2_DIR_REC_LEN(1) + EXT2_DIR_REC_LEN(2),
                     FALSE,
                     &dwBytes );

        if (!NT_SUCCESS(Status)) {
            DEBUG(DL_ERR, ( "Ext2SetParentEntry: failed to read directory.\n"));
            __leave;
        }

        ASSERT(dwBytes == EXT2_DIR_REC_LEN(1) + EXT2_DIR_REC_LEN(2));

        pParent = (PEXT2_DIR_ENTRY2)((PUCHAR)pSelf + pSelf->rec_len);

        if (pSelf->name_len == 1 && pSelf->name[0] == '.' &&
                pParent->name_len == 2 && pParent->name[0] == '.' &&
                pParent->name[1] == '.') {

            if (pParent->inode != OldParent) {
                DbgBreak();
            }
            pParent->inode = NewParent;

            Status = Ext2WriteInode(
                         IrpContext,
                         Vcb,
                         Dcb->Mcb,
                         (ULONGLONG)Offset,
                         pSelf,
                         dwBytes,
                         FALSE,
                         &dwBytes );
        } else {
            DbgBreak();
        }

    } __finally {

        if (Ext2DerefXcb(&Dcb->ReferenceCount) == 0) {
            DEBUG(DL_ERR, ( "Ext2SetParentEntry: Dcb reference goes to ZERO.\n"));
        }

        if (MainResourceAcquired)    {
            ExReleaseResourceLite(&Dcb->MainResource);
        }

        if (pSelf) {
            Ext2FreePool(pSelf, EXT2_DENTRY_MAGIC);
        }
    }

    return Status;
}

int ext3_check_dir_entry (const char * function, struct inode * dir,
                          struct ext3_dir_entry_2 * de,
                          struct buffer_head * bh,
                          unsigned long offset)
{
    const char * error_msg = NULL;
    const int rlen = ext3_rec_len_from_disk(de->rec_len);

    if (rlen < EXT3_DIR_REC_LEN(1))
        error_msg = "rec_len is smaller than minimal";
    else if (rlen % 4 != 0)
        error_msg = "rec_len % 4 != 0";
    else if (rlen < EXT3_DIR_REC_LEN(de->name_len))
        error_msg = "rec_len is too small for name_len";
    else if ((char *) de + rlen > bh->b_data + dir->i_sb->s_blocksize)
        error_msg = "directory entry across blocks";
    else if (le32_to_cpu(de->inode) >
             le32_to_cpu(EXT4_SB(dir->i_sb)->s_es->s_inodes_count))
        error_msg = "inode out of bounds";

    if (error_msg != NULL) {
        DEBUG(DL_ERR, ("%s: bad entry in directory %u: %s - "
                       "offset=%u, inode=%u, rec_len=%d, name_len=%d\n",
                       function, dir->i_ino, error_msg, offset,
                       (unsigned long) le32_to_cpu(de->inode),
                       rlen, de->name_len));
    }
    return error_msg == NULL ? 1 : 0;
}

/*
 * p is at least 6 bytes before the end of page
 */
struct ext3_dir_entry_2 *
            ext3_next_entry(struct ext3_dir_entry_2 *p)
{
    return (struct ext3_dir_entry_2 *)((char *)p +
                                       ext3_rec_len_from_disk(p->rec_len));
}

#define MAX_LFS_FILESIZE 	0x7fffffffffffffff

/*
 * Maximal extent format file size.
 * Resulting logical blkno at s_maxbytes must fit in our on-disk
 * extent format containers, within a sector_t, and within i_blocks
 * in the vfs.  ext4 inode has 48 bits of i_block in fsblock units,
 * so that won't be a limiting factor.
 *
 * Note, this does *not* consider any metadata overhead for vfs i_blocks.
 */
static loff_t ext4_max_size(int blkbits, int has_huge_files)
{
    loff_t res;
    loff_t upper_limit = MAX_LFS_FILESIZE;

    /* small i_blocks in vfs inode? */
    if (!has_huge_files || sizeof(blkcnt_t) < sizeof(u64)) {
        /*
         * CONFIG_LBD is not enabled implies the inode
         * i_block represent total blocks in 512 bytes
         * 32 == size of vfs inode i_blocks * 8
         */
        upper_limit = (1LL << 32) - 1;

        /* total blocks in file system block size */
        upper_limit >>= (blkbits - 9);
        upper_limit <<= blkbits;
    }

    /* 32-bit extent-start container, ee_block */
    res = 1LL << 32;
    res <<= blkbits;
    res -= 1;

    /* Sanity check against vm- & vfs- imposed limits */
    if (res > upper_limit)
        res = upper_limit;

    return res;
}

/*
 * Maximal extent format file size.
 * Resulting logical blkno at s_maxbytes must fit in our on-disk
 * extent format containers, within a sector_t, and within i_blocks
 * in the vfs.  ext4 inode has 48 bits of i_block in fsblock units,
 * so that won't be a limiting factor.
 *
 * Note, this does *not* consider any metadata overhead for vfs i_blocks.
 */
loff_t ext3_max_size(int blkbits, int has_huge_files)
{
    loff_t res;
    loff_t upper_limit = MAX_LFS_FILESIZE;

    /* small i_blocks in vfs inode? */
    if (!has_huge_files) {
        /*
         * CONFIG_LBD is not enabled implies the inode
         * i_block represent total blocks in 512 bytes
         * 32 == size of vfs inode i_blocks * 8
         */
        upper_limit = ((loff_t)1 << 32) - 1;

        /* total blocks in file system block size */
        upper_limit >>= (blkbits - 9);
        upper_limit <<= blkbits;
    }

    /* 32-bit extent-start container, ee_block */
    res = (loff_t)1 << 32;
    res <<= blkbits;
    res -= 1;

    /* Sanity check against vm- & vfs- imposed limits */
    if (res > upper_limit)
        res = upper_limit;

    return res;
}

/*
 * Maximal bitmap file size.  There is a direct, and {,double-,triple-}indirect
 * block limit, and also a limit of (2^48 - 1) 512-byte sectors in i_blocks.
 * We need to be 1 filesystem block less than the 2^48 sector limit.
 */
loff_t ext3_max_bitmap_size(int bits, int has_huge_files)
{
    loff_t res = EXT3_NDIR_BLOCKS;
    int meta_blocks;
    loff_t upper_limit;
    /* This is calculated to be the largest file size for a
     * dense, bitmapped file such that the total number of
     * sectors in the file, including data and all indirect blocks,
     * does not exceed 2^48 -1
     * __u32 i_blocks_lo and _u16 i_blocks_high representing the
     * total number of  512 bytes blocks of the file
     */

    if (!has_huge_files) {
        /*
         * !has_huge_files or CONFIG_LBD is not enabled
         * implies the inode i_block represent total blocks in
         * 512 bytes 32 == size of vfs inode i_blocks * 8
         */
        upper_limit = ((loff_t)1 << 32) - 1;

        /* total blocks in file system block size */
        upper_limit >>= (bits - 9);

    } else {
        /*
         * We use 48 bit ext4_inode i_blocks
         * With EXT4_HUGE_FILE_FL set the i_blocks
         * represent total number of blocks in
         * file system block size
         */
        upper_limit = ((loff_t)1 << 48) - 1;

    }

    /* indirect blocks */
    meta_blocks = 1;
    /* double indirect blocks */
    meta_blocks += 1 + ((loff_t)1 << (bits-2));
    /* tripple indirect blocks */
    meta_blocks += 1 + ((loff_t)1 << (bits-2)) + ((loff_t)1 << (2*(bits-2)));

    upper_limit -= meta_blocks;
    upper_limit <<= bits;

    res += (loff_t)1 << (bits-2);
    res += (loff_t)1 << (2*(bits-2));
    res += (loff_t)1 << (3*(bits-2));
    res <<= bits;
    if (res > upper_limit)
        res = upper_limit;

    if (res > MAX_LFS_FILESIZE)
        res = MAX_LFS_FILESIZE;

    return res;
}

blkcnt_t ext3_inode_blocks(struct ext4_inode *raw_inode,
                           struct inode *inode)
{
    blkcnt_t i_blocks ;
    struct super_block *sb = inode->i_sb;
    PEXT2_VCB Vcb = (PEXT2_VCB)sb->s_priv;

    if (EXT4_HAS_RO_COMPAT_FEATURE(sb,
                                   EXT4_FEATURE_RO_COMPAT_HUGE_FILE)) {
        /* we are using combined 48 bit field */
        i_blocks = ((u64)le16_to_cpu(raw_inode->i_blocks_high)) << 32 |
                   le32_to_cpu(raw_inode->i_blocks_lo);
        if (inode->i_flags & EXT4_HUGE_FILE_FL) {
            /* i_blocks represent file system block size */
            return i_blocks  << (BLOCK_BITS - 9);
        } else {
            return i_blocks;
        }
    } else {
        return le32_to_cpu(raw_inode->i_blocks_lo);
    }
}

int ext3_inode_blocks_set(struct ext4_inode *raw_inode,
                          struct inode * inode)
{
    u64 i_blocks = inode->i_blocks;
    struct super_block *sb = inode->i_sb;
    PEXT2_VCB Vcb = (PEXT2_VCB)sb->s_priv;

    if (i_blocks < 0x100000000) {
        /*
         * i_blocks can be represnted in a 32 bit variable
         * as multiple of 512 bytes
         */
        raw_inode->i_blocks_lo = cpu_to_le32(i_blocks);
        raw_inode->i_blocks_high = 0;
        inode->i_flags &= ~EXT4_HUGE_FILE_FL;
        return 0;
    }

    if (!EXT4_HAS_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_HUGE_FILE)) {
        EXT3_SET_RO_COMPAT_FEATURE(sb, EXT4_FEATURE_RO_COMPAT_HUGE_FILE);
        Ext2SaveSuper(NULL, Vcb);
    }

    if (i_blocks <= 0xffffffffffff) {
        /*
         * i_blocks can be represented in a 48 bit variable
         * as multiple of 512 bytes
         */
        raw_inode->i_blocks_lo = (__u32)cpu_to_le32(i_blocks);
        raw_inode->i_blocks_high = (__u16)cpu_to_le16(i_blocks >> 32);
        inode->i_flags &= ~EXT4_HUGE_FILE_FL;
    } else {
        inode->i_flags |= EXT4_HUGE_FILE_FL;
        /* i_block is stored in file system block size */
        i_blocks = i_blocks >> (BLOCK_BITS - 9);
        raw_inode->i_blocks_lo  = (__u32)cpu_to_le32(i_blocks);
        raw_inode->i_blocks_high = (__u16)cpu_to_le16(i_blocks >> 32);
    }
    return 0;
}

ext4_fsblk_t ext4_block_bitmap(struct super_block *sb,
                               struct ext4_group_desc *bg)
{
    return le32_to_cpu(bg->bg_block_bitmap_lo) |
           (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT ?
            (ext4_fsblk_t)le32_to_cpu(bg->bg_block_bitmap_hi) << 32 : 0);
}

ext4_fsblk_t ext4_inode_bitmap(struct super_block *sb,
                               struct ext4_group_desc *bg)
{
    return le32_to_cpu(bg->bg_inode_bitmap_lo) |
           (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT ?
            (ext4_fsblk_t)le32_to_cpu(bg->bg_inode_bitmap_hi) << 32 : 0);
}

ext4_fsblk_t ext4_inode_table(struct super_block *sb,
                              struct ext4_group_desc *bg)
{
    return le32_to_cpu(bg->bg_inode_table_lo) |
           (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT ?
            (ext4_fsblk_t)le32_to_cpu(bg->bg_inode_table_hi) << 32 : 0);
}

__u32 ext4_free_blks_count(struct super_block *sb,
                           struct ext4_group_desc *bg)
{
    return le16_to_cpu(bg->bg_free_blocks_count_lo) |
           (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT ?
            (__u32)le16_to_cpu(bg->bg_free_blocks_count_hi) << 16 : 0);
}

__u32 ext4_free_inodes_count(struct super_block *sb,
                             struct ext4_group_desc *bg)
{
    return le16_to_cpu(bg->bg_free_inodes_count_lo) |
           (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT ?
            (__u32)le16_to_cpu(bg->bg_free_inodes_count_hi) << 16 : 0);
}

__u32 ext4_used_dirs_count(struct super_block *sb,
                           struct ext4_group_desc *bg)
{
    return le16_to_cpu(bg->bg_used_dirs_count_lo) |
           (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT ?
            (__u32)le16_to_cpu(bg->bg_used_dirs_count_hi) << 16 : 0);
}

__u32 ext4_itable_unused_count(struct super_block *sb,
                               struct ext4_group_desc *bg)
{
    return le16_to_cpu(bg->bg_itable_unused_lo) |
           (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT ?
            (__u32)le16_to_cpu(bg->bg_itable_unused_hi) << 16 : 0);
}

void ext4_block_bitmap_set(struct super_block *sb,
                           struct ext4_group_desc *bg, ext4_fsblk_t blk)
{
    bg->bg_block_bitmap_lo = cpu_to_le32((u32)blk);
    if (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT)
        bg->bg_block_bitmap_hi = cpu_to_le32(blk >> 32);
}

void ext4_inode_bitmap_set(struct super_block *sb,
                           struct ext4_group_desc *bg, ext4_fsblk_t blk)
{
    bg->bg_inode_bitmap_lo  = cpu_to_le32((u32)blk);
    if (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT)
        bg->bg_inode_bitmap_hi = cpu_to_le32(blk >> 32);
}

void ext4_inode_table_set(struct super_block *sb,
                          struct ext4_group_desc *bg, ext4_fsblk_t blk)
{
    bg->bg_inode_table_lo = cpu_to_le32((u32)blk);
    if (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT)
        bg->bg_inode_table_hi = cpu_to_le32(blk >> 32);
}

void ext4_free_blks_set(struct super_block *sb,
                        struct ext4_group_desc *bg, __u32 count)
{
    bg->bg_free_blocks_count_lo = cpu_to_le16((__u16)count);
    if (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT)
        bg->bg_free_blocks_count_hi = cpu_to_le16(count >> 16);
}

void ext4_free_inodes_set(struct super_block *sb,
                          struct ext4_group_desc *bg, __u32 count)
{
    bg->bg_free_inodes_count_lo = cpu_to_le16((__u16)count);
    if (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT)
        bg->bg_free_inodes_count_hi = cpu_to_le16(count >> 16);
}

void ext4_used_dirs_set(struct super_block *sb,
                        struct ext4_group_desc *bg, __u32 count)
{
    bg->bg_used_dirs_count_lo = cpu_to_le16((__u16)count);
    if (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT)
        bg->bg_used_dirs_count_hi = cpu_to_le16(count >> 16);
}

void ext4_itable_unused_set(struct super_block *sb,
                            struct ext4_group_desc *bg, __u32 count)
{
    bg->bg_itable_unused_lo = cpu_to_le16((__u16)count);
    if (EXT4_DESC_SIZE(sb) >= EXT4_MIN_DESC_SIZE_64BIT)
        bg->bg_itable_unused_hi = cpu_to_le16(count >> 16);
}

static inline int test_root(ext3_group_t a, ext3_group_t b)
{
    ext3_group_t num = b;

    while (a > num)
        num *= b;
    return num == a;
}

static int ext3_group_sparse(ext3_group_t group)
{
    if (group <= 1)
        return 1;
    if (!(group & 1))
        return 0;
    return (test_root(group, 7) || test_root(group, 5) ||
            test_root(group, 3));
}

/**
 *	ext4_bg_has_super - number of blocks used by the superblock in group
 *	@sb: superblock for filesystem
 *	@group: group number to check
 *
 *	Return the number of blocks used by the superblock (primary or backup)
 *	in this group.  Currently this will be only 0 or 1.
 */
int ext3_bg_has_super(struct super_block *sb, ext3_group_t group)
{
    if (EXT4_HAS_RO_COMPAT_FEATURE(sb,
                                   EXT3_FEATURE_RO_COMPAT_SPARSE_SUPER) &&
            !ext3_group_sparse(group))
        return 0;
    return 1;
}

static unsigned long ext4_bg_num_gdb_meta(struct super_block *sb,
        ext4_group_t group)
{
    unsigned long metagroup = group / EXT4_DESC_PER_BLOCK(sb);
    ext4_group_t first = metagroup * EXT4_DESC_PER_BLOCK(sb);
    ext4_group_t last = first + EXT4_DESC_PER_BLOCK(sb) - 1;

    if (group == first || group == first + 1 || group == last)
        return 1;
    return 0;
}

static unsigned long ext4_bg_num_gdb_nometa(struct super_block *sb,
        ext4_group_t group)
{
    return ext3_bg_has_super(sb, group) ? EXT4_SB(sb)->s_gdb_count : 0;
}

/**
 *	ext4_bg_num_gdb - number of blocks used by the group table in group
 *	@sb: superblock for filesystem
 *	@group: group number to check
 *
 *	Return the number of blocks used by the group descriptor table
 *	(primary or backup) in this group.  In the future there may be a
 *	different number of descriptor blocks in each group.
 */
unsigned long ext4_bg_num_gdb(struct super_block *sb, ext4_group_t group)
{
    unsigned long first_meta_bg =
        le32_to_cpu(EXT4_SB(sb)->s_es->s_first_meta_bg);
    unsigned long metagroup = group / EXT4_DESC_PER_BLOCK(sb);

    if (!EXT4_HAS_INCOMPAT_FEATURE(sb,EXT4_FEATURE_INCOMPAT_META_BG) ||
            metagroup < first_meta_bg)
        return ext4_bg_num_gdb_nometa(sb, group);

    return ext4_bg_num_gdb_meta(sb,group);

}

ext3_fsblk_t descriptor_loc(struct super_block *sb,
                            ext3_fsblk_t logical_sb_block, unsigned int nr)
{
    struct ext4_sb_info *sbi = EXT4_SB(sb);
    ext3_group_t bg, first_meta_bg;
    int has_super = 0;

    first_meta_bg = le32_to_cpu(sbi->s_es->s_first_meta_bg);

    if (!EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_META_BG) ||
            nr < first_meta_bg)
        return logical_sb_block + nr + 1;
    bg = sbi->s_desc_per_block * nr;
    if (ext3_bg_has_super(sb, bg))
        has_super = 1;
    return (has_super + ext3_group_first_block_no(sb, bg));
}

#define ext4_set_bit(n, p) set_bit((int)(n), (unsigned long *)(p))

/*
 * The free inodes are managed by bitmaps.  A file system contains several
 * blocks groups.  Each group contains 1 bitmap block for blocks, 1 bitmap
 * block for inodes, N blocks for the inode table and data blocks.
 *
 * The file system contains group descriptors which are located after the
 * super block.  Each descriptor contains the number of the bitmap block and
 * the free blocks count in the block.
 */

/*
 * To avoid calling the atomic setbit hundreds or thousands of times, we only
 * need to use it within a single byte (to ensure we get endianness right).
 * We can use memset for the rest of the bitmap as there are no other users.
 */
void mark_bitmap_end(int start_bit, int end_bit, char *bitmap)
{
    int i;

    if (start_bit >= end_bit)
        return;

    DEBUG(DL_INF, ("mark end bits +%d through +%d used\n", start_bit, end_bit));
    for (i = start_bit; (unsigned)i < ((start_bit + 7) & ~7UL); i++)
        ext4_set_bit(i, bitmap);
    if (i < end_bit)
        memset(bitmap + (i >> 3), 0xff, (end_bit - i) >> 3);
}

/* Initializes an uninitialized inode bitmap */
unsigned ext4_init_inode_bitmap(struct super_block *sb, struct buffer_head *bh,
                                ext4_group_t block_group,
                                struct ext4_group_desc *gdp)
{
    struct ext4_sb_info *sbi = EXT4_SB(sb);

    mark_buffer_dirty(bh);

    /* If checksum is bad mark all blocks and inodes use to prevent
     * allocation, essentially implementing a per-group read-only flag. */
    if (!ext4_group_desc_csum_verify(sb, block_group, gdp)) {
        ext4_error(sb, __FUNCTION__, "Checksum bad for group %u",
                   block_group);
        ext4_free_blks_set(sb, gdp, 0);
        ext4_free_inodes_set(sb, gdp, 0);
        ext4_itable_unused_set(sb, gdp, 0);
        memset(bh->b_data, 0xff, sb->s_blocksize);
        return 0;
    }

    memset(bh->b_data, 0, (EXT4_INODES_PER_GROUP(sb) + 7) / 8);
    mark_bitmap_end(EXT4_INODES_PER_GROUP(sb), sb->s_blocksize * 8,
                    bh->b_data);
    ext4_itable_unused_set(sb, gdp, EXT4_INODES_PER_GROUP(sb));

    return EXT4_INODES_PER_GROUP(sb);
}

/*
 * Calculate the block group number and offset, given a block number
 */
void ext4_get_group_no_and_offset(struct super_block *sb, ext4_fsblk_t blocknr,
                                  ext4_group_t *blockgrpp, ext4_grpblk_t *offsetp)
{
    struct ext4_super_block *es = EXT4_SB(sb)->s_es;
    ext4_grpblk_t offset;

    blocknr = blocknr - le32_to_cpu(es->s_first_data_block);
    offset = do_div(blocknr, EXT4_BLOCKS_PER_GROUP(sb));
    if (offsetp)
        *offsetp = offset;
    if (blockgrpp)
        *blockgrpp = (ext4_grpblk_t)blocknr;

}

static int ext4_block_in_group(struct super_block *sb, ext4_fsblk_t block,
                               ext4_group_t block_group)
{
    ext4_group_t actual_group;
    ext4_get_group_no_and_offset(sb, block, &actual_group, NULL);
    if (actual_group == block_group)
        return 1;
    return 0;
}

static int ext4_group_used_meta_blocks(struct super_block *sb,
                                       ext4_group_t block_group)
{
    ext4_fsblk_t tmp;
    struct ext4_sb_info *sbi = EXT4_SB(sb);
    /* block bitmap, inode bitmap, and inode table blocks */
    int used_blocks = sbi->s_itb_per_group + 2;

    if (EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_FLEX_BG)) {
        struct ext4_group_desc *gdp;
        struct buffer_head *bh = NULL;

        gdp = ext4_get_group_desc(sb, block_group, &bh);
        if (!ext4_block_in_group(sb, ext4_block_bitmap(sb, gdp),
                                 block_group))
            used_blocks--;

        if (!ext4_block_in_group(sb, ext4_inode_bitmap(sb, gdp),
                                 block_group))
            used_blocks--;

        tmp = ext4_inode_table(sb, gdp);
        for (; tmp < ext4_inode_table(sb, gdp) +
                sbi->s_itb_per_group; tmp++) {
            if (!ext4_block_in_group(sb, tmp, block_group))
                used_blocks -= 1;
        }
        if (bh)
            fini_bh(&bh);
    }
    return used_blocks;
}

/* Initializes an uninitialized block bitmap if given, and returns the
 * number of blocks free in the group. */
unsigned ext4_init_block_bitmap(struct super_block *sb, struct buffer_head *bh,
                                ext4_group_t block_group, struct ext4_group_desc *gdp)
{
    int bit, bit_max;
    unsigned free_blocks, group_blocks;
    struct ext4_sb_info *sbi = EXT4_SB(sb);

    if (bh) {
        mark_buffer_dirty(bh);
        /* If checksum is bad mark all blocks used to prevent allocation
         * essentially implementing a per-group read-only flag. */
        if (!ext4_group_desc_csum_verify(sb, block_group, gdp)) {
            ext4_error(sb, __FUNCTION__,
                       "Checksum bad for group %u", block_group);
            ext4_free_blks_set(sb, gdp, 0);
            ext4_free_inodes_set(sb, gdp, 0);
            ext4_itable_unused_set(sb, gdp, 0);
            memset(bh->b_data, 0xff, sb->s_blocksize);
            return 0;
        }
        memset(bh->b_data, 0, sb->s_blocksize);
    }

    /* Check for superblock and gdt backups in this group */
    bit_max = ext3_bg_has_super(sb, block_group);

    if (!EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_META_BG) ||
            block_group < le32_to_cpu(sbi->s_es->s_first_meta_bg) *
            sbi->s_desc_per_block) {
        if (bit_max) {
            bit_max += ext4_bg_num_gdb(sb, block_group);
            bit_max +=
                le16_to_cpu(sbi->s_es->s_reserved_gdt_blocks);
        }
    } else { /* For META_BG_BLOCK_GROUPS */
        bit_max += ext4_bg_num_gdb(sb, block_group);
    }

    if (block_group == sbi->s_groups_count - 1) {
        /*
         * Even though mke2fs always initialize first and last group
         * if some other tool enabled the EXT4_BG_BLOCK_UNINIT we need
         * to make sure we calculate the right free blocks
         */
        group_blocks = (unsigned int)(ext3_blocks_count(sbi->s_es) -
                                      le32_to_cpu(sbi->s_es->s_first_data_block) -
                                      (EXT4_BLOCKS_PER_GROUP(sb) * (sbi->s_groups_count - 1)));
    } else {
        group_blocks = EXT4_BLOCKS_PER_GROUP(sb);
    }

    free_blocks = group_blocks - bit_max;

    if (bh) {
        ext4_fsblk_t start, tmp;
        int flex_bg = 0;

        for (bit = 0; bit < bit_max; bit++)
            ext4_set_bit(bit, bh->b_data);

        start = ext3_group_first_block_no(sb, block_group);

        if (EXT4_HAS_INCOMPAT_FEATURE(sb,
                                      EXT4_FEATURE_INCOMPAT_FLEX_BG))
            flex_bg = 1;

        /* Set bits for block and inode bitmaps, and inode table */
        tmp = ext4_block_bitmap(sb, gdp);
        if (!flex_bg || ext4_block_in_group(sb, tmp, block_group))
            ext4_set_bit(tmp - start, bh->b_data);

        tmp = ext4_inode_bitmap(sb, gdp);
        if (!flex_bg || ext4_block_in_group(sb, tmp, block_group))
            ext4_set_bit(tmp - start, bh->b_data);

        tmp = ext4_inode_table(sb, gdp);
        for (; tmp < ext4_inode_table(sb, gdp) +
                sbi->s_itb_per_group; tmp++) {
            if (!flex_bg ||
                    ext4_block_in_group(sb, tmp, block_group))
                ext4_set_bit(tmp - start, bh->b_data);
        }
        /*
         * Also if the number of blocks within the group is
         * less than the blocksize * 8 ( which is the size
         * of bitmap ), set rest of the block bitmap to 1
         */
        mark_bitmap_end(group_blocks, sb->s_blocksize * 8, bh->b_data);
    }
    return free_blocks - ext4_group_used_meta_blocks(sb, block_group);
}

/**
 * ext4_get_group_desc() -- load group descriptor from disk
 * @sb:			super block
 * @block_group:	given block group
 * @bh:			pointer to the buffer head to store the block
 *			group descriptor
 */
struct ext4_group_desc * ext4_get_group_desc(struct super_block *sb,
                    ext4_group_t block_group, struct buffer_head **bh)
{
    struct ext4_group_desc *desc = NULL;
    struct ext4_sb_info *sbi = EXT4_SB(sb);
    PEXT2_VCB vcb = sb->s_priv;
    ext4_group_t group;
    ext4_group_t offset;

    if (bh)
        *bh = NULL;

    if (block_group >= sbi->s_groups_count) {
        ext4_error(sb, "ext4_get_group_desc",
                   "block_group >= groups_count - "
                   "block_group = %u, groups_count = %u",
                   block_group, sbi->s_groups_count);

        return NULL;
    }

    __try {

        group = block_group >> EXT4_DESC_PER_BLOCK_BITS(sb);
        offset = block_group & (EXT4_DESC_PER_BLOCK(sb) - 1);

        if (!sbi->s_gd) {
            if (!Ext2LoadGroup(vcb)) {
                __leave;
            }
        } else if ( !sbi->s_gd[group].block ||
                    !sbi->s_gd[group].bh) {
            if (!Ext2LoadGroupBH(vcb)) {
                __leave;
            }
        }

        desc = (struct ext4_group_desc *)((PCHAR)sbi->s_gd[group].gd +
                                          offset * EXT4_DESC_SIZE(sb));
        if (bh) {
            atomic_inc(&sbi->s_gd[group].bh->b_count);
            *bh = sbi->s_gd[group].bh;
        }
    } __finally {
        /* do cleanup */
    }

    return desc;
}

/**
 * ext4_count_free_blocks() -- count filesystem free blocks
 * @sb:		superblock
 *
 * Adds up the number of free blocks from each block group.
 */
ext4_fsblk_t ext4_count_free_blocks(struct super_block *sb)
{
    ext4_fsblk_t desc_count;
    struct ext4_group_desc *gdp;
    struct buffer_head *bh = NULL;
    ext4_group_t i;
    ext4_group_t ngroups = EXT4_SB(sb)->s_groups_count;

    desc_count = 0;
    smp_rmb();
    for (i = 0; i < ngroups; i++) {
        gdp = ext4_get_group_desc(sb, i, &bh);
        if (!bh)
            continue;
        desc_count += ext4_free_blks_count(sb, gdp);
        fini_bh(&bh);
    }

    return desc_count;
}

unsigned long ext4_count_free_inodes(struct super_block *sb)
{
    unsigned long desc_count;
    struct ext4_group_desc *gdp;
    struct buffer_head *bh = NULL;
    ext4_group_t i;

    desc_count = 0;
    for (i = 0; i < EXT4_SB(sb)->s_groups_count; i++) {
        gdp = ext4_get_group_desc(sb, i, &bh);
        if (!bh)
            continue;
        desc_count += ext4_free_inodes_count(sb, gdp);
        fini_bh(&bh);
    }
    return desc_count;
}

/* Called at mount-time, super-block is locked */
unsigned long ext4_count_dirs(struct super_block * sb)
{
    struct ext4_group_desc *gdp;
    struct buffer_head *bh = NULL;
    unsigned long count = 0;
    ext4_group_t i;

    for (i = 0; i < EXT4_SB(sb)->s_groups_count; i++) {
        gdp = ext4_get_group_desc(sb, i, &bh);
        if (!bh)
            continue;
        count += ext4_used_dirs_count(sb, gdp);
        fini_bh(&bh);
    }
    return count;
}

/* Called at mount-time, super-block is locked */
int ext4_check_descriptors(struct super_block *sb)
{
    PEXT2_VCB            Vcb = sb->s_priv;
    struct ext4_sb_info *sbi = EXT4_SB(sb);
    ext4_fsblk_t first_block = le32_to_cpu(sbi->s_es->s_first_data_block);
    ext4_fsblk_t last_block;
    ext4_fsblk_t block_bitmap;
    ext4_fsblk_t inode_bitmap;
    ext4_fsblk_t inode_table;
    int flexbg_flag = 0;
    ext4_group_t i;

    if (EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_FLEX_BG))
        flexbg_flag = 1;

    DEBUG(DL_INF, ("Checking group descriptors"));

    for (i = 0; i < sbi->s_groups_count; i++) {

        struct buffer_head *bh = NULL;
        struct ext4_group_desc *gdp = ext4_get_group_desc(sb, i, &bh);

        if (!bh)
            continue;

        if (i == sbi->s_groups_count - 1 || flexbg_flag)
            last_block = ext3_blocks_count(sbi->s_es) - 1;
        else
            last_block = first_block +
                         (EXT3_BLOCKS_PER_GROUP(sb) - 1);

        block_bitmap = ext4_block_bitmap(sb, gdp);
        if (block_bitmap < first_block || block_bitmap > last_block) {
            printk(KERN_ERR "EXT4-fs: ext4_check_descriptors: "
                   "Block bitmap for group %u not in group "
                   "(block %llu)!\n", i, block_bitmap);
            __brelse(bh);
            return 0;
        }
        inode_bitmap = ext4_inode_bitmap(sb, gdp);
        if (inode_bitmap < first_block || inode_bitmap > last_block) {
            printk(KERN_ERR "EXT4-fs: ext4_check_descriptors: "
                   "Inode bitmap for group %u not in group "
                   "(block %llu)!\n", i, inode_bitmap);
            __brelse(bh);
            return 0;
        }
        inode_table = ext4_inode_table(sb, gdp);
        if (inode_table < first_block ||
                inode_table + sbi->s_itb_per_group - 1 > last_block) {
            printk(KERN_ERR "EXT4-fs: ext4_check_descriptors: "
                   "Inode table for group %u not in group "
                   "(block %llu)!\n", i, inode_table);
            __brelse(bh);
            return 0;
        }

        if (!ext4_group_desc_csum_verify(sb, i, gdp)) {
            printk(KERN_ERR "EXT4-fs: ext4_check_descriptors: "
                   "Checksum for group %u failed.\n", i);
        }

        if (!flexbg_flag)
            first_block += EXT4_BLOCKS_PER_GROUP(sb);

        __brelse(bh);
    }

    ext3_free_blocks_count_set(sbi->s_es, ext4_count_free_blocks(sb));
    sbi->s_es->s_free_inodes_count = cpu_to_le32(ext4_count_free_inodes(sb));
    return 1;
}
