]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - fs/ext4/resize.c
ext4: fix ext4_flush_completed_IO wait semantics
[karo-tx-linux.git] / fs / ext4 / resize.c
index a5be589c85bcc51aa7ef3660f71f8dcda01488d2..7a75e1086961eedb674b8c2bf1bb84e657f4dfa2 100644 (file)
@@ -1049,8 +1049,6 @@ static void update_backups(struct super_block *sb, int blk_off, char *data,
                goto exit_err;
        }
 
-       ext4_superblock_csum_set(sb, (struct ext4_super_block *)data);
-
        if (meta_bg == 0) {
                group = ext4_list_backups(sb, &three, &five, &seven);
                last = sbi->s_groups_count;
@@ -1183,17 +1181,12 @@ static struct buffer_head *ext4_get_bitmap(struct super_block *sb, __u64 block)
        struct buffer_head *bh = sb_getblk(sb, block);
        if (!bh)
                return NULL;
-
-       if (bitmap_uptodate(bh))
-               return bh;
-
-       lock_buffer(bh);
-       if (bh_submit_read(bh) < 0) {
-               unlock_buffer(bh);
-               brelse(bh);
-               return NULL;
+       if (!bh_uptodate_or_lock(bh)) {
+               if (bh_submit_read(bh) < 0) {
+                       brelse(bh);
+                       return NULL;
+               }
        }
-       unlock_buffer(bh);
 
        return bh;
 }
@@ -1467,6 +1460,7 @@ exit_journal:
                                   EXT4_DESC_PER_BLOCK(sb));
                int meta_bg = EXT4_HAS_INCOMPAT_FEATURE(sb,
                                EXT4_FEATURE_INCOMPAT_META_BG);
+               sector_t old_gdb = 0;
 
                update_backups(sb, sbi->s_sbh->b_blocknr, (char *)es,
                               sizeof(struct ext4_super_block), 0);
@@ -1474,8 +1468,11 @@ exit_journal:
                        struct buffer_head *gdb_bh;
 
                        gdb_bh = sbi->s_group_desc[gdb_num];
+                       if (old_gdb == gdb_bh->b_blocknr)
+                               continue;
                        update_backups(sb, gdb_bh->b_blocknr, gdb_bh->b_data,
                                       gdb_bh->b_size, meta_bg);
+                       old_gdb = gdb_bh->b_blocknr;
                }
        }
 exit:
@@ -1756,6 +1753,94 @@ int ext4_group_extend(struct super_block *sb, struct ext4_super_block *es,
        return err;
 } /* ext4_group_extend */
 
+
+static int num_desc_blocks(struct super_block *sb, ext4_group_t groups)
+{
+       return (groups + EXT4_DESC_PER_BLOCK(sb) - 1) / EXT4_DESC_PER_BLOCK(sb);
+}
+
+/*
+ * Release the resize inode and drop the resize_inode feature if there
+ * are no more reserved gdt blocks, and then convert the file system
+ * to enable meta_bg
+ */
+static int ext4_convert_meta_bg(struct super_block *sb, struct inode *inode)
+{
+       handle_t *handle;
+       struct ext4_sb_info *sbi = EXT4_SB(sb);
+       struct ext4_super_block *es = sbi->s_es;
+       struct ext4_inode_info *ei = EXT4_I(inode);
+       ext4_fsblk_t nr;
+       int i, ret, err = 0;
+       int credits = 1;
+
+       ext4_msg(sb, KERN_INFO, "Converting file system to meta_bg");
+       if (inode) {
+               if (es->s_reserved_gdt_blocks) {
+                       ext4_error(sb, "Unexpected non-zero "
+                                  "s_reserved_gdt_blocks");
+                       return -EPERM;
+               }
+
+               /* Do a quick sanity check of the resize inode */
+               if (inode->i_blocks != 1 << (inode->i_blkbits - 9))
+                       goto invalid_resize_inode;
+               for (i = 0; i < EXT4_N_BLOCKS; i++) {
+                       if (i == EXT4_DIND_BLOCK) {
+                               if (ei->i_data[i])
+                                       continue;
+                               else
+                                       goto invalid_resize_inode;
+                       }
+                       if (ei->i_data[i])
+                               goto invalid_resize_inode;
+               }
+               credits += 3;   /* block bitmap, bg descriptor, resize inode */
+       }
+
+       handle = ext4_journal_start_sb(sb, credits);
+       if (IS_ERR(handle))
+               return PTR_ERR(handle);
+
+       err = ext4_journal_get_write_access(handle, sbi->s_sbh);
+       if (err)
+               goto errout;
+
+       EXT4_CLEAR_COMPAT_FEATURE(sb, EXT4_FEATURE_COMPAT_RESIZE_INODE);
+       EXT4_SET_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_META_BG);
+       sbi->s_es->s_first_meta_bg =
+               cpu_to_le32(num_desc_blocks(sb, sbi->s_groups_count));
+
+       err = ext4_handle_dirty_super(handle, sb);
+       if (err) {
+               ext4_std_error(sb, err);
+               goto errout;
+       }
+
+       if (inode) {
+               nr = le32_to_cpu(ei->i_data[EXT4_DIND_BLOCK]);
+               ext4_free_blocks(handle, inode, NULL, nr, 1,
+                                EXT4_FREE_BLOCKS_METADATA |
+                                EXT4_FREE_BLOCKS_FORGET);
+               ei->i_data[EXT4_DIND_BLOCK] = 0;
+               inode->i_blocks = 0;
+
+               err = ext4_mark_inode_dirty(handle, inode);
+               if (err)
+                       ext4_std_error(sb, err);
+       }
+
+errout:
+       ret = ext4_journal_stop(handle);
+       if (!err)
+               err = ret;
+       return ret;
+
+invalid_resize_inode:
+       ext4_error(sb, "corrupted/inconsistent resize inode");
+       return -EINVAL;
+}
+
 /*
  * ext4_resize_fs() resizes a fs to new size specified by @n_blocks_count
  *
@@ -1772,18 +1857,27 @@ int ext4_resize_fs(struct super_block *sb, ext4_fsblk_t n_blocks_count)
        ext4_grpblk_t add, offset;
        unsigned long n_desc_blocks;
        unsigned long o_desc_blocks;
-       unsigned long desc_blocks;
        ext4_group_t o_group;
        ext4_group_t n_group;
        ext4_fsblk_t o_blocks_count;
+       ext4_fsblk_t n_blocks_count_retry = 0;
+       unsigned long last_update_time = 0;
        int err = 0, flexbg_size = 1 << sbi->s_log_groups_per_flex;
        int meta_bg;
 
+       /* 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);
+
+retry:
        o_blocks_count = ext4_blocks_count(es);
 
-       if (test_opt(sb, DEBUG))
-               ext4_msg(sb, KERN_DEBUG, "resizing filesystem from %llu "
-                      "to %llu blocks", o_blocks_count, n_blocks_count);
+       ext4_msg(sb, KERN_INFO, "resizing filesystem from %llu "
+                "to %llu blocks", o_blocks_count, n_blocks_count);
 
        if (n_blocks_count < o_blocks_count) {
                /* On-line shrinking not supported */
@@ -1798,11 +1892,8 @@ int ext4_resize_fs(struct super_block *sb, ext4_fsblk_t n_blocks_count)
        ext4_get_group_no_and_offset(sb, n_blocks_count - 1, &n_group, &offset);
        ext4_get_group_no_and_offset(sb, o_blocks_count - 1, &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;
+       n_desc_blocks = num_desc_blocks(sb, n_group + 1);
+       o_desc_blocks = num_desc_blocks(sb, sbi->s_groups_count);
 
        meta_bg = EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_META_BG);
 
@@ -1812,29 +1903,38 @@ int ext4_resize_fs(struct super_block *sb, ext4_fsblk_t n_blocks_count)
                                   "simultaneously");
                        return -EINVAL;
                }
-               if (le16_to_cpu(es->s_reserved_gdt_blocks) < desc_blocks) {
-                       ext4_warning(sb,
-                                    "No reserved GDT blocks, can't resize");
-                       return -EPERM;
+               if (n_desc_blocks > o_desc_blocks +
+                   le16_to_cpu(es->s_reserved_gdt_blocks)) {
+                       n_blocks_count_retry = n_blocks_count;
+                       n_desc_blocks = o_desc_blocks +
+                               le16_to_cpu(es->s_reserved_gdt_blocks);
+                       n_group = n_desc_blocks * EXT4_DESC_PER_BLOCK(sb);
+                       n_blocks_count = n_group * EXT4_BLOCKS_PER_GROUP(sb);
+                       n_group--; /* set to last group number */
                }
-               resize_inode = ext4_iget(sb, EXT4_RESIZE_INO);
+
+               if (!resize_inode)
+                       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);
                }
-       } else if (!meta_bg) {
-               ext4_warning(sb, "File system features do not permit "
-                            "online resize");
-               return -EPERM;
        }
 
-       /* 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;
+       if ((!resize_inode && !meta_bg) || n_blocks_count == o_blocks_count) {
+               err = ext4_convert_meta_bg(sb, resize_inode);
+               if (err)
+                       goto out;
+               if (resize_inode) {
+                       iput(resize_inode);
+                       resize_inode = NULL;
+               }
+               if (n_blocks_count_retry) {
+                       n_blocks_count = n_blocks_count_retry;
+                       n_blocks_count_retry = 0;
+                       goto retry;
+               }
        }
-       brelse(bh);
 
        /* extend the last group */
        if (n_group == o_group)
@@ -1869,6 +1969,13 @@ int ext4_resize_fs(struct super_block *sb, ext4_fsblk_t n_blocks_count)
         */
        while (ext4_setup_next_flex_gd(sb, flex_gd, n_blocks_count,
                                              flexbg_size)) {
+               if (jiffies - last_update_time > HZ * 10) {
+                       if (last_update_time)
+                               ext4_msg(sb, KERN_INFO,
+                                        "resized to %llu blocks",
+                                        ext4_blocks_count(es));
+                       last_update_time = jiffies;
+               }
                if (ext4_alloc_group_tables(sb, flex_gd, flexbg_size) != 0)
                        break;
                err = ext4_flex_group_add(sb, resize_inode, flex_gd);
@@ -1876,13 +1983,19 @@ int ext4_resize_fs(struct super_block *sb, ext4_fsblk_t n_blocks_count)
                        break;
        }
 
+       if (!err && n_blocks_count_retry) {
+               n_blocks_count = n_blocks_count_retry;
+               n_blocks_count_retry = 0;
+               free_flex_gd(flex_gd);
+               flex_gd = NULL;
+               goto retry;
+       }
+
 out:
        if (flex_gd)
                free_flex_gd(flex_gd);
        if (resize_inode != NULL)
                iput(resize_inode);
-       if (test_opt(sb, DEBUG))
-               ext4_msg(sb, KERN_DEBUG, "resized filesystem from %llu "
-                      "upto %llu blocks", o_blocks_count, n_blocks_count);
+       ext4_msg(sb, KERN_INFO, "resized filesystem to %llu", n_blocks_count);
        return err;
 }