]> Pileus Git - ~andy/linux/blobdiff - fs/ext4/resize.c
Merge tag 'fbdev-fixes-for-3.3-1' of git://github.com/schandinat/linux-2.6
[~andy/linux] / fs / ext4 / resize.c
index 996780ab4f4e83cdfc114e83ab8cad35242f4813..f9d948f0eb861f08de3ef7b589b401846dc627f6 100644 (file)
@@ -134,6 +134,172 @@ static int verify_group_input(struct super_block *sb,
        return err;
 }
 
+/*
+ * ext4_new_flex_group_data is used by 64bit-resize interface to add a flex
+ * group each time.
+ */
+struct ext4_new_flex_group_data {
+       struct ext4_new_group_data *groups;     /* new_group_data for groups
+                                                  in the flex group */
+       __u16 *bg_flags;                        /* block group flags of groups
+                                                  in @groups */
+       ext4_group_t count;                     /* number of groups in @groups
+                                                */
+};
+
+/*
+ * alloc_flex_gd() allocates a ext4_new_flex_group_data with size of
+ * @flexbg_size.
+ *
+ * Returns NULL on failure otherwise address of the allocated structure.
+ */
+static struct ext4_new_flex_group_data *alloc_flex_gd(unsigned long flexbg_size)
+{
+       struct ext4_new_flex_group_data *flex_gd;
+
+       flex_gd = kmalloc(sizeof(*flex_gd), GFP_NOFS);
+       if (flex_gd == NULL)
+               goto out3;
+
+       flex_gd->count = flexbg_size;
+
+       flex_gd->groups = kmalloc(sizeof(struct ext4_new_group_data) *
+                                 flexbg_size, GFP_NOFS);
+       if (flex_gd->groups == NULL)
+               goto out2;
+
+       flex_gd->bg_flags = kmalloc(flexbg_size * sizeof(__u16), GFP_NOFS);
+       if (flex_gd->bg_flags == NULL)
+               goto out1;
+
+       return flex_gd;
+
+out1:
+       kfree(flex_gd->groups);
+out2:
+       kfree(flex_gd);
+out3:
+       return NULL;
+}
+
+static void free_flex_gd(struct ext4_new_flex_group_data *flex_gd)
+{
+       kfree(flex_gd->bg_flags);
+       kfree(flex_gd->groups);
+       kfree(flex_gd);
+}
+
+/*
+ * ext4_alloc_group_tables() allocates block bitmaps, inode bitmaps
+ * and inode tables for a flex group.
+ *
+ * This function is used by 64bit-resize.  Note that this function allocates
+ * group tables from the 1st group of groups contained by @flexgd, which may
+ * be a partial of a flex group.
+ *
+ * @sb: super block of fs to which the groups belongs
+ */
+static void ext4_alloc_group_tables(struct super_block *sb,
+                               struct ext4_new_flex_group_data *flex_gd,
+                               int flexbg_size)
+{
+       struct ext4_new_group_data *group_data = flex_gd->groups;
+       struct ext4_super_block *es = EXT4_SB(sb)->s_es;
+       ext4_fsblk_t start_blk;
+       ext4_fsblk_t last_blk;
+       ext4_group_t src_group;
+       ext4_group_t bb_index = 0;
+       ext4_group_t ib_index = 0;
+       ext4_group_t it_index = 0;
+       ext4_group_t group;
+       ext4_group_t last_group;
+       unsigned overhead;
+
+       BUG_ON(flex_gd->count == 0 || group_data == NULL);
+
+       src_group = group_data[0].group;
+       last_group  = src_group + flex_gd->count - 1;
+
+       BUG_ON((flexbg_size > 1) && ((src_group & ~(flexbg_size - 1)) !=
+              (last_group & ~(flexbg_size - 1))));
+next_group:
+       group = group_data[0].group;
+       start_blk = ext4_group_first_block_no(sb, src_group);
+       last_blk = start_blk + group_data[src_group - group].blocks_count;
+
+       overhead = ext4_bg_has_super(sb, src_group) ?
+                  (1 + ext4_bg_num_gdb(sb, src_group) +
+                   le16_to_cpu(es->s_reserved_gdt_blocks)) : 0;
+
+       start_blk += overhead;
+
+       BUG_ON(src_group >= group_data[0].group + flex_gd->count);
+       /* We collect contiguous blocks as much as possible. */
+       src_group++;
+       for (; src_group <= last_group; src_group++)
+               if (!ext4_bg_has_super(sb, src_group))
+                       last_blk += group_data[src_group - group].blocks_count;
+               else
+                       break;
+
+       /* Allocate block bitmaps */
+       for (; bb_index < flex_gd->count; bb_index++) {
+               if (start_blk >= last_blk)
+                       goto next_group;
+               group_data[bb_index].block_bitmap = start_blk++;
+               ext4_get_group_no_and_offset(sb, start_blk - 1, &group, NULL);
+               group -= group_data[0].group;
+               group_data[group].free_blocks_count--;
+               if (flexbg_size > 1)
+                       flex_gd->bg_flags[group] &= ~EXT4_BG_BLOCK_UNINIT;
+       }
+
+       /* Allocate inode bitmaps */
+       for (; ib_index < flex_gd->count; ib_index++) {
+               if (start_blk >= last_blk)
+                       goto next_group;
+               group_data[ib_index].inode_bitmap = start_blk++;
+               ext4_get_group_no_and_offset(sb, start_blk - 1, &group, NULL);
+               group -= group_data[0].group;
+               group_data[group].free_blocks_count--;
+               if (flexbg_size > 1)
+                       flex_gd->bg_flags[group] &= ~EXT4_BG_BLOCK_UNINIT;
+       }
+
+       /* Allocate inode tables */
+       for (; it_index < flex_gd->count; it_index++) {
+               if (start_blk + EXT4_SB(sb)->s_itb_per_group > last_blk)
+                       goto next_group;
+               group_data[it_index].inode_table = start_blk;
+               ext4_get_group_no_and_offset(sb, start_blk, &group, NULL);
+               group -= group_data[0].group;
+               group_data[group].free_blocks_count -=
+                                       EXT4_SB(sb)->s_itb_per_group;
+               if (flexbg_size > 1)
+                       flex_gd->bg_flags[group] &= ~EXT4_BG_BLOCK_UNINIT;
+
+               start_blk += EXT4_SB(sb)->s_itb_per_group;
+       }
+
+       if (test_opt(sb, DEBUG)) {
+               int i;
+               group = group_data[0].group;
+
+               printk(KERN_DEBUG "EXT4-fs: adding a flex group with "
+                      "%d groups, flexbg size is %d:\n", flex_gd->count,
+                      flexbg_size);
+
+               for (i = 0; i < flex_gd->count; i++) {
+                       printk(KERN_DEBUG "adding %s group %u: %u "
+                              "blocks (%d free)\n",
+                              ext4_bg_has_super(sb, group + i) ? "normal" :
+                              "no-super", group + i,
+                              group_data[i].blocks_count,
+                              group_data[i].free_blocks_count);
+               }
+       }
+}
+
 static struct buffer_head *bclean(handle_t *handle, struct super_block *sb,
                                  ext4_fsblk_t blk)
 {
@@ -179,131 +345,250 @@ static int extend_or_restart_transaction(handle_t *handle, int thresh)
 }
 
 /*
- * Set up the block and inode bitmaps, and the inode table for the new group.
+ * set_flexbg_block_bitmap() mark @count blocks starting from @block used.
+ *
+ * Helper function for ext4_setup_new_group_blocks() which set .
+ *
+ * @sb: super block
+ * @handle: journal handle
+ * @flex_gd: flex group data
+ */
+static int set_flexbg_block_bitmap(struct super_block *sb, handle_t *handle,
+                       struct ext4_new_flex_group_data *flex_gd,
+                       ext4_fsblk_t block, ext4_group_t count)
+{
+       ext4_group_t count2;
+
+       ext4_debug("mark blocks [%llu/%u] used\n", block, count);
+       for (count2 = count; count > 0; count -= count2, block += count2) {
+               ext4_fsblk_t start;
+               struct buffer_head *bh;
+               ext4_group_t group;
+               int err;
+
+               ext4_get_group_no_and_offset(sb, block, &group, NULL);
+               start = ext4_group_first_block_no(sb, group);
+               group -= flex_gd->groups[0].group;
+
+               count2 = sb->s_blocksize * 8 - (block - start);
+               if (count2 > count)
+                       count2 = count;
+
+               if (flex_gd->bg_flags[group] & EXT4_BG_BLOCK_UNINIT) {
+                       BUG_ON(flex_gd->count > 1);
+                       continue;
+               }
+
+               err = extend_or_restart_transaction(handle, 1);
+               if (err)
+                       return err;
+
+               bh = sb_getblk(sb, flex_gd->groups[group].block_bitmap);
+               if (!bh)
+                       return -EIO;
+
+               err = ext4_journal_get_write_access(handle, bh);
+               if (err)
+                       return err;
+               ext4_debug("mark block bitmap %#04llx (+%llu/%u)\n", block,
+                          block - start, count2);
+               ext4_set_bits(bh->b_data, block - start, count2);
+
+               err = ext4_handle_dirty_metadata(handle, NULL, bh);
+               if (unlikely(err))
+                       return err;
+               brelse(bh);
+       }
+
+       return 0;
+}
+
+/*
+ * Set up the block and inode bitmaps, and the inode table for the new groups.
  * This doesn't need to be part of the main transaction, since we are only
  * changing blocks outside the actual filesystem.  We still do journaling to
  * ensure the recovery is correct in case of a failure just after resize.
  * If any part of this fails, we simply abort the resize.
+ *
+ * setup_new_flex_group_blocks handles a flex group as follow:
+ *  1. copy super block and GDT, and initialize group tables if necessary.
+ *     In this step, we only set bits in blocks bitmaps for blocks taken by
+ *     super block and GDT.
+ *  2. allocate group tables in block bitmaps, that is, set bits in block
+ *     bitmap for blocks taken by group tables.
  */
-static int setup_new_group_blocks(struct super_block *sb,
-                                 struct ext4_new_group_data *input)
+static int setup_new_flex_group_blocks(struct super_block *sb,
+                               struct ext4_new_flex_group_data *flex_gd)
 {
+       int group_table_count[] = {1, 1, EXT4_SB(sb)->s_itb_per_group};
+       ext4_fsblk_t start;
+       ext4_fsblk_t block;
        struct ext4_sb_info *sbi = EXT4_SB(sb);
-       ext4_fsblk_t start = ext4_group_first_block_no(sb, input->group);
-       int reserved_gdb = ext4_bg_has_super(sb, input->group) ?
-               le16_to_cpu(sbi->s_es->s_reserved_gdt_blocks) : 0;
-       unsigned long gdblocks = ext4_bg_num_gdb(sb, input->group);
-       struct buffer_head *bh;
+       struct ext4_super_block *es = sbi->s_es;
+       struct ext4_new_group_data *group_data = flex_gd->groups;
+       __u16 *bg_flags = flex_gd->bg_flags;
        handle_t *handle;
-       ext4_fsblk_t block;
-       ext4_grpblk_t bit;
-       int i;
-       int err = 0, err2;
+       ext4_group_t group, count;
+       struct buffer_head *bh = NULL;
+       int reserved_gdb, i, j, err = 0, err2;
+
+       BUG_ON(!flex_gd->count || !group_data ||
+              group_data[0].group != sbi->s_groups_count);
+
+       reserved_gdb = le16_to_cpu(es->s_reserved_gdt_blocks);
 
        /* This transaction may be extended/restarted along the way */
        handle = ext4_journal_start_sb(sb, EXT4_MAX_TRANS_DATA);
-
        if (IS_ERR(handle))
                return PTR_ERR(handle);
 
-       BUG_ON(input->group != sbi->s_groups_count);
+       group = group_data[0].group;
+       for (i = 0; i < flex_gd->count; i++, group++) {
+               unsigned long gdblocks;
 
-       /* Copy all of the GDT blocks into the backup in this group */
-       for (i = 0, bit = 1, block = start + 1;
-            i < gdblocks; i++, block++, bit++) {
-               struct buffer_head *gdb;
+               gdblocks = ext4_bg_num_gdb(sb, group);
+               start = ext4_group_first_block_no(sb, group);
 
-               ext4_debug("update backup group %#04llx (+%d)\n", block, bit);
-               err = extend_or_restart_transaction(handle, 1);
-               if (err)
-                       goto exit_journal;
+               /* Copy all of the GDT blocks into the backup in this group */
+               for (j = 0, block = start + 1; j < gdblocks; j++, block++) {
+                       struct buffer_head *gdb;
 
-               gdb = sb_getblk(sb, block);
-               if (!gdb) {
-                       err = -EIO;
-                       goto exit_journal;
-               }
-               if ((err = ext4_journal_get_write_access(handle, gdb))) {
+                       ext4_debug("update backup group %#04llx\n", block);
+                       err = extend_or_restart_transaction(handle, 1);
+                       if (err)
+                               goto out;
+
+                       gdb = sb_getblk(sb, block);
+                       if (!gdb) {
+                               err = -EIO;
+                               goto out;
+                       }
+
+                       err = ext4_journal_get_write_access(handle, gdb);
+                       if (err) {
+                               brelse(gdb);
+                               goto out;
+                       }
+                       memcpy(gdb->b_data, sbi->s_group_desc[j]->b_data,
+                              gdb->b_size);
+                       set_buffer_uptodate(gdb);
+
+                       err = ext4_handle_dirty_metadata(handle, NULL, gdb);
+                       if (unlikely(err)) {
+                               brelse(gdb);
+                               goto out;
+                       }
                        brelse(gdb);
-                       goto exit_journal;
                }
-               memcpy(gdb->b_data, sbi->s_group_desc[i]->b_data, gdb->b_size);
-               set_buffer_uptodate(gdb);
-               err = ext4_handle_dirty_metadata(handle, NULL, gdb);
-               if (unlikely(err)) {
-                       brelse(gdb);
-                       goto exit_journal;
+
+               /* Zero out all of the reserved backup group descriptor
+                * table blocks
+                */
+               if (ext4_bg_has_super(sb, group)) {
+                       err = sb_issue_zeroout(sb, gdblocks + start + 1,
+                                       reserved_gdb, GFP_NOFS);
+                       if (err)
+                               goto out;
                }
-               brelse(gdb);
-       }
 
-       /* Zero out all of the reserved backup group descriptor table blocks */
-       ext4_debug("clear inode table blocks %#04llx -> %#04lx\n",
-                       block, sbi->s_itb_per_group);
-       err = sb_issue_zeroout(sb, gdblocks + start + 1, reserved_gdb,
-                              GFP_NOFS);
-       if (err)
-               goto exit_journal;
+               /* Initialize group tables of the grop @group */
+               if (!(bg_flags[i] & EXT4_BG_INODE_ZEROED))
+                       goto handle_bb;
 
-       err = extend_or_restart_transaction(handle, 2);
-       if (err)
-               goto exit_journal;
+               /* Zero out all of the inode table blocks */
+               block = group_data[i].inode_table;
+               ext4_debug("clear inode table blocks %#04llx -> %#04lx\n",
+                          block, sbi->s_itb_per_group);
+               err = sb_issue_zeroout(sb, block, sbi->s_itb_per_group,
+                                      GFP_NOFS);
+               if (err)
+                       goto out;
 
-       bh = bclean(handle, sb, input->block_bitmap);
-       if (IS_ERR(bh)) {
-               err = PTR_ERR(bh);
-               goto exit_journal;
-       }
+handle_bb:
+               if (bg_flags[i] & EXT4_BG_BLOCK_UNINIT)
+                       goto handle_ib;
 
-       if (ext4_bg_has_super(sb, input->group)) {
-               ext4_debug("mark backup group tables %#04llx (+0)\n", start);
-               ext4_set_bits(bh->b_data, 0, gdblocks + reserved_gdb + 1);
-       }
+               /* Initialize block bitmap of the @group */
+               block = group_data[i].block_bitmap;
+               err = extend_or_restart_transaction(handle, 1);
+               if (err)
+                       goto out;
 
-       ext4_debug("mark block bitmap %#04llx (+%llu)\n", input->block_bitmap,
-                  input->block_bitmap - start);
-       ext4_set_bit(input->block_bitmap - start, bh->b_data);
-       ext4_debug("mark inode bitmap %#04llx (+%llu)\n", input->inode_bitmap,
-                  input->inode_bitmap - start);
-       ext4_set_bit(input->inode_bitmap - start, bh->b_data);
-
-       /* Zero out all of the inode table blocks */
-       block = input->inode_table;
-       ext4_debug("clear inode table blocks %#04llx -> %#04lx\n",
-                       block, sbi->s_itb_per_group);
-       err = sb_issue_zeroout(sb, block, sbi->s_itb_per_group, GFP_NOFS);
-       if (err)
-               goto exit_bh;
-       ext4_set_bits(bh->b_data, input->inode_table - start,
-                     sbi->s_itb_per_group);
+               bh = bclean(handle, sb, block);
+               if (IS_ERR(bh)) {
+                       err = PTR_ERR(bh);
+                       goto out;
+               }
+               if (ext4_bg_has_super(sb, group)) {
+                       ext4_debug("mark backup superblock %#04llx (+0)\n",
+                                  start);
+                       ext4_set_bits(bh->b_data, 0, gdblocks + reserved_gdb +
+                                                    1);
+               }
+               ext4_mark_bitmap_end(group_data[i].blocks_count,
+                                    sb->s_blocksize * 8, bh->b_data);
+               err = ext4_handle_dirty_metadata(handle, NULL, bh);
+               if (err)
+                       goto out;
+               brelse(bh);
 
+handle_ib:
+               if (bg_flags[i] & EXT4_BG_INODE_UNINIT)
+                       continue;
 
-       ext4_mark_bitmap_end(input->blocks_count, sb->s_blocksize * 8,
-                            bh->b_data);
-       err = ext4_handle_dirty_metadata(handle, NULL, bh);
-       if (unlikely(err)) {
-               ext4_std_error(sb, err);
-               goto exit_bh;
+               /* Initialize inode bitmap of the @group */
+               block = group_data[i].inode_bitmap;
+               err = extend_or_restart_transaction(handle, 1);
+               if (err)
+                       goto out;
+               /* Mark unused entries in inode bitmap used */
+               bh = bclean(handle, sb, block);
+               if (IS_ERR(bh)) {
+                       err = PTR_ERR(bh);
+                       goto out;
+               }
+
+               ext4_mark_bitmap_end(EXT4_INODES_PER_GROUP(sb),
+                                    sb->s_blocksize * 8, bh->b_data);
+               err = ext4_handle_dirty_metadata(handle, NULL, bh);
+               if (err)
+                       goto out;
+               brelse(bh);
        }
-       brelse(bh);
-       /* Mark unused entries in inode bitmap used */
-       ext4_debug("clear inode bitmap %#04llx (+%llu)\n",
-                  input->inode_bitmap, input->inode_bitmap - start);
-       if (IS_ERR(bh = bclean(handle, sb, input->inode_bitmap))) {
-               err = PTR_ERR(bh);
-               goto exit_journal;
+       bh = NULL;
+
+       /* Mark group tables in block bitmap */
+       for (j = 0; j < GROUP_TABLE_COUNT; j++) {
+               count = group_table_count[j];
+               start = (&group_data[0].block_bitmap)[j];
+               block = start;
+               for (i = 1; i < flex_gd->count; i++) {
+                       block += group_table_count[j];
+                       if (block == (&group_data[i].block_bitmap)[j]) {
+                               count += group_table_count[j];
+                               continue;
+                       }
+                       err = set_flexbg_block_bitmap(sb, handle,
+                                               flex_gd, start, count);
+                       if (err)
+                               goto out;
+                       count = group_table_count[j];
+                       start = group_data[i].block_bitmap;
+                       block = start;
+               }
+
+               if (count) {
+                       err = set_flexbg_block_bitmap(sb, handle,
+                                               flex_gd, start, count);
+                       if (err)
+                               goto out;
+               }
        }
 
-       ext4_mark_bitmap_end(EXT4_INODES_PER_GROUP(sb), sb->s_blocksize * 8,
-                            bh->b_data);
-       err = ext4_handle_dirty_metadata(handle, NULL, bh);
-       if (unlikely(err))
-               ext4_std_error(sb, err);
-exit_bh:
+out:
        brelse(bh);
-
-exit_journal:
-       if ((err2 = ext4_journal_stop(handle)) && !err)
+       err2 = ext4_journal_stop(handle);
+       if (err2 && !err)
                err = err2;
 
        return err;
@@ -351,10 +636,10 @@ static unsigned ext4_list_backups(struct super_block *sb, unsigned *three,
  * groups in current filesystem that have BACKUPS, or -ve error code.
  */
 static int verify_reserved_gdb(struct super_block *sb,
+                              ext4_group_t end,
                               struct buffer_head *primary)
 {
        const ext4_fsblk_t blk = primary->b_blocknr;
-       const ext4_group_t end = EXT4_SB(sb)->s_groups_count;
        unsigned three = 1;
        unsigned five = 5;
        unsigned seven = 7;
@@ -429,7 +714,7 @@ static int add_new_gdb(handle_t *handle, struct inode *inode,
        if (!gdb_bh)
                return -EIO;
 
-       gdbackups = verify_reserved_gdb(sb, gdb_bh);
+       gdbackups = verify_reserved_gdb(sb, group, gdb_bh);
        if (gdbackups < 0) {
                err = gdbackups;
                goto exit_bh;
@@ -592,7 +877,8 @@ static int reserve_backup_gdb(handle_t *handle, struct inode *inode,
                        err = -EIO;
                        goto exit_bh;
                }
-               if ((gdbackups = verify_reserved_gdb(sb, primary[res])) < 0) {
+               gdbackups = verify_reserved_gdb(sb, group, primary[res]);
+               if (gdbackups < 0) {
                        brelse(primary[res]);
                        err = gdbackups;
                        goto exit_bh;
@@ -735,6 +1021,348 @@ exit_err:
        }
 }
 
+/*
+ * ext4_add_new_descs() adds @count group descriptor of groups
+ * starting at @group
+ *
+ * @handle: journal handle
+ * @sb: super block
+ * @group: the group no. of the first group desc to be added
+ * @resize_inode: the resize inode
+ * @count: number of group descriptors to be added
+ */
+static int ext4_add_new_descs(handle_t *handle, struct super_block *sb,
+                             ext4_group_t group, struct inode *resize_inode,
+                             ext4_group_t count)
+{
+       struct ext4_sb_info *sbi = EXT4_SB(sb);
+       struct ext4_super_block *es = sbi->s_es;
+       struct buffer_head *gdb_bh;
+       int i, gdb_off, gdb_num, err = 0;
+
+       for (i = 0; i < count; i++, group++) {
+               int reserved_gdb = ext4_bg_has_super(sb, group) ?
+                       le16_to_cpu(es->s_reserved_gdt_blocks) : 0;
+
+               gdb_off = group % EXT4_DESC_PER_BLOCK(sb);
+               gdb_num = group / EXT4_DESC_PER_BLOCK(sb);
+
+               /*
+                * We will only either add reserved group blocks to a backup group
+                * or remove reserved blocks for the first group in a new group block.
+                * Doing both would be mean more complex code, and sane people don't
+                * use non-sparse filesystems anymore.  This is already checked above.
+                */
+               if (gdb_off) {
+                       gdb_bh = sbi->s_group_desc[gdb_num];
+                       err = ext4_journal_get_write_access(handle, gdb_bh);
+
+                       if (!err && reserved_gdb && ext4_bg_num_gdb(sb, group))
+                               err = reserve_backup_gdb(handle, resize_inode, group);
+               } else
+                       err = add_new_gdb(handle, resize_inode, group);
+               if (err)
+                       break;
+       }
+       return err;
+}
+
+/*
+ * ext4_setup_new_descs() will set up the group descriptor descriptors of a flex bg
+ */
+static int ext4_setup_new_descs(handle_t *handle, struct super_block *sb,
+                               struct ext4_new_flex_group_data *flex_gd)
+{
+       struct ext4_new_group_data      *group_data = flex_gd->groups;
+       struct ext4_group_desc          *gdp;
+       struct ext4_sb_info             *sbi = EXT4_SB(sb);
+       struct buffer_head              *gdb_bh;
+       ext4_group_t                    group;
+       __u16                           *bg_flags = flex_gd->bg_flags;
+       int                             i, gdb_off, gdb_num, err = 0;
+       
+
+       for (i = 0; i < flex_gd->count; i++, group_data++, bg_flags++) {
+               group = group_data->group;
+
+               gdb_off = group % EXT4_DESC_PER_BLOCK(sb);
+               gdb_num = group / EXT4_DESC_PER_BLOCK(sb);
+
+               /*
+                * get_write_access() has been called on gdb_bh by ext4_add_new_desc().
+                */
+               gdb_bh = sbi->s_group_desc[gdb_num];
+               /* Update group descriptor block for new group */
+               gdp = (struct ext4_group_desc *)((char *)gdb_bh->b_data +
+                                                gdb_off * EXT4_DESC_SIZE(sb));
+
+               memset(gdp, 0, EXT4_DESC_SIZE(sb));
+               ext4_block_bitmap_set(sb, gdp, group_data->block_bitmap);
+               ext4_inode_bitmap_set(sb, gdp, group_data->inode_bitmap);
+               ext4_inode_table_set(sb, gdp, group_data->inode_table);
+               ext4_free_group_clusters_set(sb, gdp,
+                                            EXT4_B2C(sbi, group_data->free_blocks_count));
+               ext4_free_inodes_set(sb, gdp, EXT4_INODES_PER_GROUP(sb));
+               gdp->bg_flags = cpu_to_le16(*bg_flags);
+               gdp->bg_checksum = ext4_group_desc_csum(sbi, group, gdp);
+
+               err = ext4_handle_dirty_metadata(handle, NULL, gdb_bh);
+               if (unlikely(err)) {
+                       ext4_std_error(sb, err);
+                       break;
+               }
+
+               /*
+                * We can allocate memory for mb_alloc based on the new group
+                * descriptor
+                */
+               err = ext4_mb_add_groupinfo(sb, group, gdp);
+               if (err)
+                       break;
+       }
+       return err;
+}
+
+/*
+ * ext4_update_super() updates the super block so that the newly added
+ * groups can be seen by the filesystem.
+ *
+ * @sb: super block
+ * @flex_gd: new added groups
+ */
+static void ext4_update_super(struct super_block *sb,
+                            struct ext4_new_flex_group_data *flex_gd)
+{
+       ext4_fsblk_t blocks_count = 0;
+       ext4_fsblk_t free_blocks = 0;
+       ext4_fsblk_t reserved_blocks = 0;
+       struct ext4_new_group_data *group_data = flex_gd->groups;
+       struct ext4_sb_info *sbi = EXT4_SB(sb);
+       struct ext4_super_block *es = sbi->s_es;
+       int i;
+
+       BUG_ON(flex_gd->count == 0 || group_data == NULL);
+       /*
+        * Make the new blocks and inodes valid next.  We do this before
+        * increasing the group count so that once the group is enabled,
+        * all of its blocks and inodes are already valid.
+        *
+        * We always allocate group-by-group, then block-by-block or
+        * inode-by-inode within a group, so enabling these
+        * blocks/inodes before the group is live won't actually let us
+        * allocate the new space yet.
+        */
+       for (i = 0; i < flex_gd->count; i++) {
+               blocks_count += group_data[i].blocks_count;
+               free_blocks += group_data[i].free_blocks_count;
+       }
+
+       reserved_blocks = ext4_r_blocks_count(es) * 100;
+       do_div(reserved_blocks, ext4_blocks_count(es));
+       reserved_blocks *= blocks_count;
+       do_div(reserved_blocks, 100);
+
+       ext4_blocks_count_set(es, ext4_blocks_count(es) + blocks_count);
+       le32_add_cpu(&es->s_inodes_count, EXT4_INODES_PER_GROUP(sb) *
+                    flex_gd->count);
+
+       /*
+        * We need to protect s_groups_count against other CPUs seeing
+        * inconsistent state in the superblock.
+        *
+        * The precise rules we use are:
+        *
+        * * Writers must perform a smp_wmb() after updating all
+        *   dependent data and before modifying the groups count
+        *
+        * * Readers must perform an smp_rmb() after reading the groups
+        *   count and before reading any dependent data.
+        *
+        * NB. These rules can be relaxed when checking the group count
+        * while freeing data, as we can only allocate from a block
+        * group after serialising against the group count, and we can
+        * only then free after serialising in turn against that
+        * allocation.
+        */
+       smp_wmb();
+
+       /* Update the global fs size fields */
+       sbi->s_groups_count += flex_gd->count;
+
+       /* Update the reserved block counts only once the new group is
+        * active. */
+       ext4_r_blocks_count_set(es, ext4_r_blocks_count(es) +
+                               reserved_blocks);
+
+       /* Update the free space counts */
+       percpu_counter_add(&sbi->s_freeclusters_counter,
+                          EXT4_B2C(sbi, free_blocks));
+       percpu_counter_add(&sbi->s_freeinodes_counter,
+                          EXT4_INODES_PER_GROUP(sb) * flex_gd->count);
+
+       if (EXT4_HAS_INCOMPAT_FEATURE(sb,
+                                     EXT4_FEATURE_INCOMPAT_FLEX_BG) &&
+           sbi->s_log_groups_per_flex) {
+               ext4_group_t flex_group;
+               flex_group = ext4_flex_group(sbi, group_data[0].group);
+               atomic_add(EXT4_B2C(sbi, free_blocks),
+                          &sbi->s_flex_groups[flex_group].free_clusters);
+               atomic_add(EXT4_INODES_PER_GROUP(sb) * flex_gd->count,
+                          &sbi->s_flex_groups[flex_group].free_inodes);
+       }
+
+       if (test_opt(sb, DEBUG))
+               printk(KERN_DEBUG "EXT4-fs: added group %u:"
+                      "%llu blocks(%llu free %llu reserved)\n", flex_gd->count,
+                      blocks_count, free_blocks, reserved_blocks);
+}
+
+/* Add a flex group to an fs. Ensure we handle all possible error conditions
+ * _before_ we start modifying the filesystem, because we cannot abort the
+ * transaction and not have it write the data to disk.
+ */
+static int ext4_flex_group_add(struct super_block *sb,
+                              struct inode *resize_inode,
+                              struct ext4_new_flex_group_data *flex_gd)
+{
+       struct ext4_sb_info *sbi = EXT4_SB(sb);
+       struct ext4_super_block *es = sbi->s_es;
+       ext4_fsblk_t o_blocks_count;
+       ext4_grpblk_t last;
+       ext4_group_t group;
+       handle_t *handle;
+       unsigned reserved_gdb;
+       int err = 0, err2 = 0, credit;
+
+       BUG_ON(!flex_gd->count || !flex_gd->groups || !flex_gd->bg_flags);
+
+       reserved_gdb = le16_to_cpu(es->s_reserved_gdt_blocks);
+       o_blocks_count = ext4_blocks_count(es);
+       ext4_get_group_no_and_offset(sb, o_blocks_count, &group, &last);
+       BUG_ON(last);
+
+       err = setup_new_flex_group_blocks(sb, flex_gd);
+       if (err)
+               goto exit;
+       /*
+        * We will always be modifying at least the superblock and  GDT
+        * block.  If we are adding a group past the last current GDT block,
+        * we will also modify the inode and the dindirect block.  If we
+        * are adding a group with superblock/GDT backups  we will also
+        * modify each of the reserved GDT dindirect blocks.
+        */
+       credit = flex_gd->count * 4 + reserved_gdb;
+       handle = ext4_journal_start_sb(sb, credit);
+       if (IS_ERR(handle)) {
+               err = PTR_ERR(handle);
+               goto exit;
+       }
+
+       err = ext4_journal_get_write_access(handle, sbi->s_sbh);
+       if (err)
+               goto exit_journal;
+
+       group = flex_gd->groups[0].group;
+       BUG_ON(group != EXT4_SB(sb)->s_groups_count);
+       err = ext4_add_new_descs(handle, sb, group,
+                               resize_inode, flex_gd->count);
+       if (err)
+               goto exit_journal;
+
+       err = ext4_setup_new_descs(handle, sb, flex_gd);
+       if (err)
+               goto exit_journal;
+
+       ext4_update_super(sb, flex_gd);
+
+       err = ext4_handle_dirty_super(handle, sb);
+
+exit_journal:
+       err2 = ext4_journal_stop(handle);
+       if (!err)
+               err = err2;
+
+       if (!err) {
+               int i;
+               update_backups(sb, sbi->s_sbh->b_blocknr, (char *)es,
+                              sizeof(struct ext4_super_block));
+               for (i = 0; i < flex_gd->count; i++, group++) {
+                       struct buffer_head *gdb_bh;
+                       int gdb_num;
+                       gdb_num = group / EXT4_BLOCKS_PER_GROUP(sb);
+                       gdb_bh = sbi->s_group_desc[gdb_num];
+                       update_backups(sb, gdb_bh->b_blocknr, gdb_bh->b_data,
+                                      gdb_bh->b_size);
+               }
+       }
+exit:
+       return err;
+}
+
+static int ext4_setup_next_flex_gd(struct super_block *sb,
+                                   struct ext4_new_flex_group_data *flex_gd,
+                                   ext4_fsblk_t n_blocks_count,
+                                   unsigned long flexbg_size)
+{
+       struct ext4_super_block *es = EXT4_SB(sb)->s_es;
+       struct ext4_new_group_data *group_data = flex_gd->groups;
+       ext4_fsblk_t o_blocks_count;
+       ext4_group_t n_group;
+       ext4_group_t group;
+       ext4_group_t last_group;
+       ext4_grpblk_t last;
+       ext4_grpblk_t blocks_per_group;
+       unsigned long i;
+
+       blocks_per_group = EXT4_BLOCKS_PER_GROUP(sb);
+
+       o_blocks_count = ext4_blocks_count(es);
+
+       if (o_blocks_count == n_blocks_count)
+               return 0;
+
+       ext4_get_group_no_and_offset(sb, o_blocks_count, &group, &last);
+       BUG_ON(last);
+       ext4_get_group_no_and_offset(sb, n_blocks_count - 1, &n_group, &last);
+
+       last_group = group | (flexbg_size - 1);
+       if (last_group > n_group)
+               last_group = n_group;
+
+       flex_gd->count = last_group - group + 1;
+
+       for (i = 0; i < flex_gd->count; i++) {
+               int overhead;
+
+               group_data[i].group = group + i;
+               group_data[i].blocks_count = blocks_per_group;
+               overhead = ext4_bg_has_super(sb, group + i) ?
+                          (1 + ext4_bg_num_gdb(sb, group + i) +
+                           le16_to_cpu(es->s_reserved_gdt_blocks)) : 0;
+               group_data[i].free_blocks_count = blocks_per_group - overhead;
+               if (EXT4_HAS_RO_COMPAT_FEATURE(sb,
+                                              EXT4_FEATURE_RO_COMPAT_GDT_CSUM))
+                       flex_gd->bg_flags[i] = EXT4_BG_BLOCK_UNINIT |
+                                              EXT4_BG_INODE_UNINIT;
+               else
+                       flex_gd->bg_flags[i] = EXT4_BG_INODE_ZEROED;
+       }
+
+       if (last_group == n_group &&
+           EXT4_HAS_RO_COMPAT_FEATURE(sb,
+                                      EXT4_FEATURE_RO_COMPAT_GDT_CSUM))
+               /* We need to initialize block bitmap of last group. */
+               flex_gd->bg_flags[i - 1] &= ~EXT4_BG_BLOCK_UNINIT;
+
+       if ((last_group == n_group) && (last != blocks_per_group - 1)) {
+               group_data[i - 1].blocks_count = last + 1;
+               group_data[i - 1].free_blocks_count -= blocks_per_group-
+                                       last - 1;
+       }
+
+       return 1;
+}
+
 /* Add group descriptor data to an existing or new group descriptor block.
  * Ensure we handle all possible error conditions _before_ we start modifying
  * the filesystem, because we cannot abort the transaction and not have it
@@ -750,16 +1378,15 @@ exit_err:
  */
 int ext4_group_add(struct super_block *sb, struct ext4_new_group_data *input)
 {
+       struct ext4_new_flex_group_data flex_gd;
        struct ext4_sb_info *sbi = EXT4_SB(sb);
        struct ext4_super_block *es = sbi->s_es;
        int reserved_gdb = ext4_bg_has_super(sb, input->group) ?
                le16_to_cpu(es->s_reserved_gdt_blocks) : 0;
-       struct buffer_head *primary = NULL;
-       struct ext4_group_desc *gdp;
        struct inode *inode = NULL;
-       handle_t *handle;
        int gdb_off, gdb_num;
-       int err, err2;
+       int err;
+       __u16 bg_flags = 0;
 
        gdb_num = input->group / EXT4_DESC_PER_BLOCK(sb);
        gdb_off = input->group % EXT4_DESC_PER_BLOCK(sb);
@@ -798,175 +1425,69 @@ int ext4_group_add(struct super_block *sb, struct ext4_new_group_data *input)
        }
 
 
-       if ((err = verify_group_input(sb, input)))
-               goto exit_put;
+       err = verify_group_input(sb, input);
+       if (err)
+               goto out;
 
-       if ((err = setup_new_group_blocks(sb, input)))
-               goto exit_put;
+       flex_gd.count = 1;
+       flex_gd.groups = input;
+       flex_gd.bg_flags = &bg_flags;
+       err = ext4_flex_group_add(sb, inode, &flex_gd);
+out:
+       iput(inode);
+       return err;
+} /* ext4_group_add */
 
-       /*
-        * We will always be modifying at least the superblock and a GDT
-        * block.  If we are adding a group past the last current GDT block,
-        * we will also modify the inode and the dindirect block.  If we
-        * are adding a group with superblock/GDT backups  we will also
-        * modify each of the reserved GDT dindirect blocks.
+/*
+ * extend a group without checking assuming that checking has been done.
+ */
+static int ext4_group_extend_no_check(struct super_block *sb,
+                                     ext4_fsblk_t o_blocks_count, ext4_grpblk_t add)
+{
+       struct ext4_super_block *es = EXT4_SB(sb)->s_es;
+       handle_t *handle;
+       int err = 0, err2;
+
+       /* We will update the superblock, one block bitmap, and
+        * one group descriptor via ext4_group_add_blocks().
         */
-       handle = ext4_journal_start_sb(sb,
-                                      ext4_bg_has_super(sb, input->group) ?
-                                      3 + reserved_gdb : 4);
+       handle = ext4_journal_start_sb(sb, 3);
        if (IS_ERR(handle)) {
                err = PTR_ERR(handle);
-               goto exit_put;
+               ext4_warning(sb, "error %d on journal start", err);
+               return err;
        }
 
-       if ((err = ext4_journal_get_write_access(handle, sbi->s_sbh)))
-               goto exit_journal;
-
-        /*
-         * We will only either add reserved group blocks to a backup group
-         * or remove reserved blocks for the first group in a new group block.
-         * Doing both would be mean more complex code, and sane people don't
-         * use non-sparse filesystems anymore.  This is already checked above.
-         */
-       if (gdb_off) {
-               primary = sbi->s_group_desc[gdb_num];
-               if ((err = ext4_journal_get_write_access(handle, primary)))
-                       goto exit_journal;
-
-               if (reserved_gdb && ext4_bg_num_gdb(sb, input->group)) {
-                       err = reserve_backup_gdb(handle, inode, input->group);
-                       if (err)
-                               goto exit_journal;
-               }
-       } else {
-               /*
-                * Note that we can access new group descriptor block safely
-                * only if add_new_gdb() succeeds.
-                */
-               err = add_new_gdb(handle, inode, input->group);
-               if (err)
-                       goto exit_journal;
-               primary = sbi->s_group_desc[gdb_num];
+       err = ext4_journal_get_write_access(handle, EXT4_SB(sb)->s_sbh);
+       if (err) {
+               ext4_warning(sb, "error %d on journal write access", err);
+               goto errout;
        }
 
-        /*
-         * OK, now we've set up the new group.  Time to make it active.
-         *
-         * so we have to be safe wrt. concurrent accesses the group
-         * data.  So we need to be careful to set all of the relevant
-         * group descriptor data etc. *before* we enable the group.
-         *
-         * The key field here is sbi->s_groups_count: as long as
-         * that retains its old value, nobody is going to access the new
-         * group.
-         *
-         * So first we update all the descriptor metadata for the new
-         * group; then we update the total disk blocks count; then we
-         * update the groups count to enable the group; then finally we
-         * update the free space counts so that the system can start
-         * using the new disk blocks.
-         */
-
-       /* Update group descriptor block for new group */
-       gdp = (struct ext4_group_desc *)((char *)primary->b_data +
-                                        gdb_off * EXT4_DESC_SIZE(sb));
-
-       memset(gdp, 0, EXT4_DESC_SIZE(sb));
-       ext4_block_bitmap_set(sb, gdp, input->block_bitmap); /* LV FIXME */
-       ext4_inode_bitmap_set(sb, gdp, input->inode_bitmap); /* LV FIXME */
-       ext4_inode_table_set(sb, gdp, input->inode_table); /* LV FIXME */
-       ext4_free_group_clusters_set(sb, gdp, input->free_blocks_count);
-       ext4_free_inodes_set(sb, gdp, EXT4_INODES_PER_GROUP(sb));
-       gdp->bg_flags = cpu_to_le16(EXT4_BG_INODE_ZEROED);
-       gdp->bg_checksum = ext4_group_desc_csum(sbi, input->group, gdp);
-
-       /*
-        * We can allocate memory for mb_alloc based on the new group
-        * descriptor
-        */
-       err = ext4_mb_add_groupinfo(sb, input->group, gdp);
+       ext4_blocks_count_set(es, o_blocks_count + add);
+       ext4_debug("freeing blocks %llu through %llu\n", o_blocks_count,
+                  o_blocks_count + add);
+       /* We add the blocks to the bitmap and set the group need init bit */
+       err = ext4_group_add_blocks(handle, sb, o_blocks_count, add);
        if (err)
-               goto exit_journal;
-
-       /*
-        * Make the new blocks and inodes valid next.  We do this before
-        * increasing the group count so that once the group is enabled,
-        * all of its blocks and inodes are already valid.
-        *
-        * We always allocate group-by-group, then block-by-block or
-        * inode-by-inode within a group, so enabling these
-        * blocks/inodes before the group is live won't actually let us
-        * allocate the new space yet.
-        */
-       ext4_blocks_count_set(es, ext4_blocks_count(es) +
-               input->blocks_count);
-       le32_add_cpu(&es->s_inodes_count, EXT4_INODES_PER_GROUP(sb));
-
-       /*
-        * We need to protect s_groups_count against other CPUs seeing
-        * inconsistent state in the superblock.
-        *
-        * The precise rules we use are:
-        *
-        * * Writers must perform a smp_wmb() after updating all dependent
-        *   data and before modifying the groups count
-        *
-        * * Readers must perform an smp_rmb() after reading the groups count
-        *   and before reading any dependent data.
-        *
-        * NB. These rules can be relaxed when checking the group count
-        * while freeing data, as we can only allocate from a block
-        * group after serialising against the group count, and we can
-        * only then free after serialising in turn against that
-        * allocation.
-        */
-       smp_wmb();
-
-       /* Update the global fs size fields */
-       sbi->s_groups_count++;
-
-       err = ext4_handle_dirty_metadata(handle, NULL, primary);
-       if (unlikely(err)) {
-               ext4_std_error(sb, err);
-               goto exit_journal;
-       }
-
-       /* Update the reserved block counts only once the new group is
-        * active. */
-       ext4_r_blocks_count_set(es, ext4_r_blocks_count(es) +
-               input->reserved_blocks);
-
-       /* Update the free space counts */
-       percpu_counter_add(&sbi->s_freeclusters_counter,
-                          EXT4_B2C(sbi, input->free_blocks_count));
-       percpu_counter_add(&sbi->s_freeinodes_counter,
-                          EXT4_INODES_PER_GROUP(sb));
-
-       if (EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_FLEX_BG) &&
-           sbi->s_log_groups_per_flex) {
-               ext4_group_t flex_group;
-               flex_group = ext4_flex_group(sbi, input->group);
-               atomic_add(EXT4_B2C(sbi, input->free_blocks_count),
-                          &sbi->s_flex_groups[flex_group].free_clusters);
-               atomic_add(EXT4_INODES_PER_GROUP(sb),
-                          &sbi->s_flex_groups[flex_group].free_inodes);
-       }
-
+               goto errout;
        ext4_handle_dirty_super(handle, sb);
-
-exit_journal:
-       if ((err2 = ext4_journal_stop(handle)) && !err)
+       ext4_debug("freed blocks %llu through %llu\n", o_blocks_count,
+                  o_blocks_count + add);
+errout:
+       err2 = ext4_journal_stop(handle);
+       if (err2 && !err)
                err = err2;
-       if (!err && primary) {
-               update_backups(sb, sbi->s_sbh->b_blocknr, (char *)es,
+
+       if (!err) {
+               if (test_opt(sb, DEBUG))
+                       printk(KERN_DEBUG "EXT4-fs: extended group to %llu "
+                              "blocks\n", ext4_blocks_count(es));
+               update_backups(sb, EXT4_SB(sb)->s_sbh->b_blocknr, (char *)es,
                               sizeof(struct ext4_super_block));
-               update_backups(sb, primary->b_blocknr, primary->b_data,
-                              primary->b_size);
        }
-exit_put:
-       iput(inode);
        return err;
-} /* ext4_group_add */
+}
 
 /*
  * Extend the filesystem to the new number of blocks specified.  This entry
@@ -985,8 +1506,7 @@ int ext4_group_extend(struct super_block *sb, struct ext4_super_block *es,
        ext4_grpblk_t last;
        ext4_grpblk_t add;
        struct buffer_head *bh;
-       handle_t *handle;
-       int err, err2;
+       int err;
        ext4_group_t group;
 
        o_blocks_count = ext4_blocks_count(es);
@@ -1042,42 +1562,119 @@ int ext4_group_extend(struct super_block *sb, struct ext4_super_block *es,
        }
        brelse(bh);
 
-       /* We will update the superblock, one block bitmap, and
-        * one group descriptor via ext4_free_blocks().
-        */
-       handle = ext4_journal_start_sb(sb, 3);
-       if (IS_ERR(handle)) {
-               err = PTR_ERR(handle);
-               ext4_warning(sb, "error %d on journal start", err);
-               goto exit_put;
+       err = ext4_group_extend_no_check(sb, o_blocks_count, add);
+       return err;
+} /* ext4_group_extend */
+
+/*
+ * ext4_resize_fs() resizes a fs to new size specified by @n_blocks_count
+ *
+ * @sb: super block of the fs to be resized
+ * @n_blocks_count: the number of blocks resides in the resized fs
+ */
+int ext4_resize_fs(struct super_block *sb, ext4_fsblk_t n_blocks_count)
+{
+       struct ext4_new_flex_group_data *flex_gd = NULL;
+       struct ext4_sb_info *sbi = EXT4_SB(sb);
+       struct ext4_super_block *es = sbi->s_es;
+       struct buffer_head *bh;
+       struct inode *resize_inode;
+       ext4_fsblk_t o_blocks_count;
+       ext4_group_t o_group;
+       ext4_group_t n_group;
+       ext4_grpblk_t offset;
+       unsigned long n_desc_blocks;
+       unsigned long o_desc_blocks;
+       unsigned long desc_blocks;
+       int err = 0, flexbg_size = 1;
+
+       o_blocks_count = ext4_blocks_count(es);
+
+       if (test_opt(sb, DEBUG))
+               printk(KERN_DEBUG "EXT4-fs: resizing filesystem from %llu "
+                      "upto %llu blocks\n", o_blocks_count, n_blocks_count);
+
+       if (n_blocks_count < o_blocks_count) {
+               /* On-line shrinking not supported */
+               ext4_warning(sb, "can't shrink FS - resize aborted");
+               return -EINVAL;
        }
 
-       if ((err = ext4_journal_get_write_access(handle,
-                                                EXT4_SB(sb)->s_sbh))) {
-               ext4_warning(sb, "error %d on journal write access", err);
-               ext4_journal_stop(handle);
-               goto exit_put;
+       if (n_blocks_count == o_blocks_count)
+               /* Nothing need to do */
+               return 0;
+
+       ext4_get_group_no_and_offset(sb, n_blocks_count - 1, &n_group, &offset);
+       ext4_get_group_no_and_offset(sb, o_blocks_count, &o_group, &offset);
+
+       n_desc_blocks = (n_group + EXT4_DESC_PER_BLOCK(sb)) /
+                       EXT4_DESC_PER_BLOCK(sb);
+       o_desc_blocks = (sbi->s_groups_count + EXT4_DESC_PER_BLOCK(sb) - 1) /
+                       EXT4_DESC_PER_BLOCK(sb);
+       desc_blocks = n_desc_blocks - o_desc_blocks;
+
+       if (desc_blocks &&
+           (!EXT4_HAS_COMPAT_FEATURE(sb, EXT4_FEATURE_COMPAT_RESIZE_INODE) ||
+            le16_to_cpu(es->s_reserved_gdt_blocks) < desc_blocks)) {
+               ext4_warning(sb, "No reserved GDT blocks, can't resize");
+               return -EPERM;
        }
-       ext4_blocks_count_set(es, o_blocks_count + add);
-       ext4_debug("freeing blocks %llu through %llu\n", o_blocks_count,
-                  o_blocks_count + add);
-       /* We add the blocks to the bitmap and set the group need init bit */
-       err = ext4_group_add_blocks(handle, sb, o_blocks_count, add);
-       ext4_handle_dirty_super(handle, sb);
-       ext4_debug("freed blocks %llu through %llu\n", o_blocks_count,
-                  o_blocks_count + add);
-       err2 = ext4_journal_stop(handle);
-       if (!err && err2)
-               err = err2;
 
-       if (err)
-               goto exit_put;
+       resize_inode = ext4_iget(sb, EXT4_RESIZE_INO);
+       if (IS_ERR(resize_inode)) {
+               ext4_warning(sb, "Error opening resize inode");
+               return PTR_ERR(resize_inode);
+       }
 
+       /* See if the device is actually as big as what was requested */
+       bh = sb_bread(sb, n_blocks_count - 1);
+       if (!bh) {
+               ext4_warning(sb, "can't read last block, resize aborted");
+               return -ENOSPC;
+       }
+       brelse(bh);
+
+       if (offset != 0) {
+               /* extend the last group */
+               ext4_grpblk_t add;
+               add = EXT4_BLOCKS_PER_GROUP(sb) - offset;
+               err = ext4_group_extend_no_check(sb, o_blocks_count, add);
+               if (err)
+                       goto out;
+       }
+
+       if (EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_FLEX_BG) &&
+           es->s_log_groups_per_flex)
+               flexbg_size = 1 << es->s_log_groups_per_flex;
+
+       o_blocks_count = ext4_blocks_count(es);
+       if (o_blocks_count == n_blocks_count)
+               goto out;
+
+       flex_gd = alloc_flex_gd(flexbg_size);
+       if (flex_gd == NULL) {
+               err = -ENOMEM;
+               goto out;
+       }
+
+       /* Add flex groups. Note that a regular group is a
+        * flex group with 1 group.
+        */
+       while (ext4_setup_next_flex_gd(sb, flex_gd, n_blocks_count,
+                                             flexbg_size)) {
+               ext4_alloc_group_tables(sb, flex_gd, flexbg_size);
+               err = ext4_flex_group_add(sb, resize_inode, flex_gd);
+               if (unlikely(err))
+                       break;
+       }
+
+out:
+       if (flex_gd)
+               free_flex_gd(flex_gd);
+
+       iput(resize_inode);
        if (test_opt(sb, DEBUG))
-               printk(KERN_DEBUG "EXT4-fs: extended group to %llu blocks\n",
-                      ext4_blocks_count(es));
-       update_backups(sb, EXT4_SB(sb)->s_sbh->b_blocknr, (char *)es,
-                      sizeof(struct ext4_super_block));
-exit_put:
+               printk(KERN_DEBUG "EXT4-fs: resized filesystem from %llu "
+                      "upto %llu blocks\n", o_blocks_count, n_blocks_count);
        return err;
-} /* ext4_group_extend */
+}