ext4: add a function which sets up a block group descriptors of a flex bg
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / fs / ext4 / resize.c
index 996780ab4f4e83cdfc114e83ab8cad35242f4813..098bdb8e97cb616f0dc9ff9cd8cfefc2063ca83d 100644 (file)
@@ -134,6 +134,61 @@ 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);
+}
+
 static struct buffer_head *bclean(handle_t *handle, struct super_block *sb,
                                  ext4_fsblk_t blk)
 {
@@ -178,6 +233,256 @@ static int extend_or_restart_transaction(handle_t *handle, int thresh)
        return 0;
 }
 
+/*
+ * 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_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);
+       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_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);
+
+       group = group_data[0].group;
+       for (i = 0; i < flex_gd->count; i++, group++) {
+               unsigned long gdblocks;
+
+               gdblocks = ext4_bg_num_gdb(sb, group);
+               start = ext4_group_first_block_no(sb, group);
+
+               /* 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;
+
+                       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);
+               }
+
+               /* 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;
+               }
+
+               /* Initialize group tables of the grop @group */
+               if (!(bg_flags[i] & EXT4_BG_INODE_ZEROED))
+                       goto handle_bb;
+
+               /* 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;
+
+handle_bb:
+               if (bg_flags[i] & EXT4_BG_BLOCK_UNINIT)
+                       goto handle_ib;
+
+               /* Initialize block bitmap of the @group */
+               block = group_data[i].block_bitmap;
+               err = extend_or_restart_transaction(handle, 1);
+               if (err)
+                       goto out;
+
+               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;
+
+               /* 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);
+       }
+       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;
+               }
+       }
+
+out:
+       brelse(bh);
+       err2 = ext4_journal_stop(handle);
+       if (err2 && !err)
+               err = err2;
+
+       return err;
+}
+
 /*
  * Set up the block and inode bitmaps, and the inode table for the new group.
  * This doesn't need to be part of the main transaction, since we are only
@@ -735,6 +1040,108 @@ 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;
+}
+
 /* 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
@@ -968,6 +1375,57 @@ exit_put:
        return err;
 } /* ext4_group_add */
 
+/*
+ * 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, 3);
+       if (IS_ERR(handle)) {
+               err = PTR_ERR(handle);
+               ext4_warning(sb, "error %d on journal start", err);
+               return err;
+       }
+
+       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;
+       }
+
+       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 errout;
+       ext4_handle_dirty_super(handle, sb);
+       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) {
+               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));
+       }
+       return err;
+}
+
 /*
  * Extend the filesystem to the new number of blocks specified.  This entry
  * point is only used to extend the current filesystem to the end of the last