ext4: fix bb_prealloc_list corruption due to wrong group locking
[safe/jmp/linux-2.6] / fs / ext4 / mballoc.c
index fd2294d..9f61e62 100644 (file)
@@ -1447,7 +1447,7 @@ static void ext4_mb_measure_extent(struct ext4_allocation_context *ac,
        struct ext4_free_extent *gex = &ac->ac_g_ex;
 
        BUG_ON(ex->fe_len <= 0);
-       BUG_ON(ex->fe_len >= EXT4_BLOCKS_PER_GROUP(ac->ac_sb));
+       BUG_ON(ex->fe_len > EXT4_BLOCKS_PER_GROUP(ac->ac_sb));
        BUG_ON(ex->fe_start >= EXT4_BLOCKS_PER_GROUP(ac->ac_sb));
        BUG_ON(ac->ac_status != AC_STATUS_CONTINUE);
 
@@ -2878,8 +2878,9 @@ static void release_blocks_on_commit(journal_t *journal, transaction_t *txn)
                discard_block = (ext4_fsblk_t) entry->group * EXT4_BLOCKS_PER_GROUP(sb)
                        + entry->start_blk
                        + le32_to_cpu(EXT4_SB(sb)->s_es->s_first_data_block);
-               trace_mark(ext4_discard_blocks, "dev %s blk %llu count %u", sb->s_id,
-                          (unsigned long long) discard_block, entry->count);
+               trace_mark(ext4_discard_blocks, "dev %s blk %llu count %u",
+                          sb->s_id, (unsigned long long) discard_block,
+                          entry->count);
                sb_issue_discard(sb, discard_block, entry->count);
 
                kmem_cache_free(ext4_free_ext_cachep, entry);
@@ -3024,7 +3025,7 @@ ext4_mb_mark_diskspace_used(struct ext4_allocation_context *ac,
                goto out_err;
 
        ext4_debug("using block group %u(%d)\n", ac->ac_b_ex.fe_group,
-                       gdp->bg_free_blocks_count);
+                       ext4_free_blks_count(sb, gdp));
 
        err = ext4_journal_get_write_access(handle, gdp_bh);
        if (err)
@@ -3291,7 +3292,7 @@ ext4_mb_normalize_request(struct ext4_allocation_context *ac,
        }
        BUG_ON(start + size <= ac->ac_o_ex.fe_logical &&
                        start > ac->ac_o_ex.fe_logical);
-       BUG_ON(size <= 0 || size >= EXT4_BLOCKS_PER_GROUP(ac->ac_sb));
+       BUG_ON(size <= 0 || size > EXT4_BLOCKS_PER_GROUP(ac->ac_sb));
 
        /* now prepare goal request */
 
@@ -3588,6 +3589,7 @@ static void ext4_mb_put_pa(struct ext4_allocation_context *ac,
                        struct super_block *sb, struct ext4_prealloc_space *pa)
 {
        ext4_group_t grp;
+       ext4_fsblk_t grp_blk;
 
        if (!atomic_dec_and_test(&pa->pa_count) || pa->pa_free != 0)
                return;
@@ -3602,8 +3604,12 @@ static void ext4_mb_put_pa(struct ext4_allocation_context *ac,
        pa->pa_deleted = 1;
        spin_unlock(&pa->pa_lock);
 
-       /* -1 is to protect from crossing allocation group */
-       ext4_get_group_no_and_offset(sb, pa->pa_pstart - 1, &grp, NULL);
+       grp_blk = pa->pa_pstart;
+       /* If linear, pa_pstart may be in the next group when pa is used up */
+       if (pa->pa_linear)
+               grp_blk--;
+
+       ext4_get_group_no_and_offset(sb, grp_blk, &grp, NULL);
 
        /*
         * possible race:
@@ -3692,11 +3698,17 @@ ext4_mb_new_inode_pa(struct ext4_allocation_context *ac)
        pa->pa_free = pa->pa_len;
        atomic_set(&pa->pa_count, 1);
        spin_lock_init(&pa->pa_lock);
+       INIT_LIST_HEAD(&pa->pa_inode_list);
+       INIT_LIST_HEAD(&pa->pa_group_list);
        pa->pa_deleted = 0;
        pa->pa_linear = 0;
 
        mb_debug("new inode pa %p: %llu/%u for %u\n", pa,
                        pa->pa_pstart, pa->pa_len, pa->pa_lstart);
+       trace_mark(ext4_mb_new_inode_pa,
+                  "dev %s ino %lu pstart %llu len %u lstart %u",
+                  sb->s_id, ac->ac_inode->i_ino,
+                  pa->pa_pstart, pa->pa_len, pa->pa_lstart);
 
        ext4_mb_use_inode_pa(ac, pa);
        atomic_add(pa->pa_free, &EXT4_SB(sb)->s_mb_preallocated);
@@ -3750,11 +3762,14 @@ ext4_mb_new_group_pa(struct ext4_allocation_context *ac)
        atomic_set(&pa->pa_count, 1);
        spin_lock_init(&pa->pa_lock);
        INIT_LIST_HEAD(&pa->pa_inode_list);
+       INIT_LIST_HEAD(&pa->pa_group_list);
        pa->pa_deleted = 0;
        pa->pa_linear = 1;
 
        mb_debug("new group pa %p: %llu/%u for %u\n", pa,
-                       pa->pa_pstart, pa->pa_len, pa->pa_lstart);
+                pa->pa_pstart, pa->pa_len, pa->pa_lstart);
+       trace_mark(ext4_mb_new_group_pa, "dev %s pstart %llu len %u lstart %u",
+                  sb->s_id, pa->pa_pstart, pa->pa_len, pa->pa_lstart);
 
        ext4_mb_use_group_pa(ac, pa);
        atomic_add(pa->pa_free, &EXT4_SB(sb)->s_mb_preallocated);
@@ -3807,12 +3822,14 @@ ext4_mb_release_inode_pa(struct ext4_buddy *e4b, struct buffer_head *bitmap_bh,
        unsigned int next;
        ext4_group_t group;
        ext4_grpblk_t bit;
+       unsigned long long grp_blk_start;
        sector_t start;
        int err = 0;
        int free = 0;
 
        BUG_ON(pa->pa_deleted == 0);
        ext4_get_group_no_and_offset(sb, pa->pa_pstart, &group, &bit);
+       grp_blk_start = pa->pa_pstart - bit;
        BUG_ON(group != e4b->bd_group && pa->pa_len != 0);
        end = bit + pa->pa_len;
 
@@ -3842,6 +3859,10 @@ ext4_mb_release_inode_pa(struct ext4_buddy *e4b, struct buffer_head *bitmap_bh,
                        ext4_mb_store_history(ac);
                }
 
+               trace_mark(ext4_mb_release_inode_pa,
+                          "dev %s ino %lu block %llu count %u",
+                          sb->s_id, pa->pa_inode->i_ino, grp_blk_start + bit,
+                          next - bit);
                mb_free_blocks(pa->pa_inode, e4b, bit, next - bit);
                bit = next + 1;
        }
@@ -3875,6 +3896,8 @@ ext4_mb_release_group_pa(struct ext4_buddy *e4b,
        if (ac)
                ac->ac_op = EXT4_MB_HISTORY_DISCARD;
 
+       trace_mark(ext4_mb_release_group_pa, "dev %s pstart %llu len %d",
+                  sb->s_id, pa->pa_pstart, pa->pa_len);
        BUG_ON(pa->pa_deleted == 0);
        ext4_get_group_no_and_offset(sb, pa->pa_pstart, &group, &bit);
        BUG_ON(group != e4b->bd_group && pa->pa_len != 0);
@@ -4040,6 +4063,8 @@ void ext4_discard_preallocations(struct inode *inode)
        }
 
        mb_debug("discard preallocation for inode %lu\n", inode->i_ino);
+       trace_mark(ext4_discard_preallocations, "dev %s ino %lu", sb->s_id,
+                  inode->i_ino);
 
        INIT_LIST_HEAD(&list);
 
@@ -4459,23 +4484,26 @@ static int ext4_mb_release_context(struct ext4_allocation_context *ac)
                        pa->pa_free -= ac->ac_b_ex.fe_len;
                        pa->pa_len -= ac->ac_b_ex.fe_len;
                        spin_unlock(&pa->pa_lock);
-                       /*
-                        * We want to add the pa to the right bucket.
-                        * Remove it from the list and while adding
-                        * make sure the list to which we are adding
-                        * doesn't grow big.
-                        */
-                       if (likely(pa->pa_free)) {
-                               spin_lock(pa->pa_obj_lock);
-                               list_del_rcu(&pa->pa_inode_list);
-                               spin_unlock(pa->pa_obj_lock);
-                               ext4_mb_add_n_trim(ac);
-                       }
                }
-               ext4_mb_put_pa(ac, ac->ac_sb, pa);
        }
        if (ac->alloc_semp)
                up_read(ac->alloc_semp);
+       if (pa) {
+               /*
+                * We want to add the pa to the right bucket.
+                * Remove it from the list and while adding
+                * make sure the list to which we are adding
+                * doesn't grow big.  We need to release
+                * alloc_semp before calling ext4_mb_add_n_trim()
+                */
+               if (pa->pa_linear && likely(pa->pa_free)) {
+                       spin_lock(pa->pa_obj_lock);
+                       list_del_rcu(&pa->pa_inode_list);
+                       spin_unlock(pa->pa_obj_lock);
+                       ext4_mb_add_n_trim(ac);
+               }
+               ext4_mb_put_pa(ac, ac->ac_sb, pa);
+       }
        if (ac->ac_bitmap_page)
                page_cache_release(ac->ac_bitmap_page);
        if (ac->ac_buddy_page)
@@ -4492,6 +4520,8 @@ static int ext4_mb_discard_preallocations(struct super_block *sb, int needed)
        int ret;
        int freed = 0;
 
+       trace_mark(ext4_mb_discard_preallocations, "dev %s needed %d",
+                  sb->s_id, needed);
        for (i = 0; i < EXT4_SB(sb)->s_groups_count && needed > 0; i++) {
                ret = ext4_mb_discard_group_preallocations(sb, i, needed);
                freed += ret;
@@ -4520,6 +4550,18 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
        sb = ar->inode->i_sb;
        sbi = EXT4_SB(sb);
 
+       trace_mark(ext4_request_blocks, "dev %s flags %u len %u ino %lu "
+                  "lblk %llu goal %llu lleft %llu lright %llu "
+                  "pleft %llu pright %llu ",
+                  sb->s_id, ar->flags, ar->len,
+                  ar->inode ? ar->inode->i_ino : 0,
+                  (unsigned long long) ar->logical,
+                  (unsigned long long) ar->goal,
+                  (unsigned long long) ar->lleft,
+                  (unsigned long long) ar->lright,
+                  (unsigned long long) ar->pleft,
+                  (unsigned long long) ar->pright);
+
        if (!EXT4_I(ar->inode)->i_delalloc_reserved_flag) {
                /*
                 * With delalloc we already reserved the blocks
@@ -4541,7 +4583,7 @@ ext4_fsblk_t ext4_mb_new_blocks(handle_t *handle,
        }
        if (ar->len == 0) {
                *errp = -EDQUOT;
-               return 0;
+               goto out3;
        }
        inquota = ar->len;
 
@@ -4614,6 +4656,26 @@ out2:
 out1:
        if (ar->len < inquota)
                DQUOT_FREE_BLOCK(ar->inode, inquota - ar->len);
+out3:
+       if (!ar->len) {
+               if (!EXT4_I(ar->inode)->i_delalloc_reserved_flag)
+                       /* release all the reserved blocks if non delalloc */
+                       percpu_counter_sub(&sbi->s_dirtyblocks_counter,
+                                               reserv_blks);
+       }
+
+       trace_mark(ext4_allocate_blocks,
+                  "dev %s block %llu flags %u len %u ino %lu "
+                  "logical %llu goal %llu lleft %llu lright %llu "
+                  "pleft %llu pright %llu ",
+                  sb->s_id, (unsigned long long) block,
+                  ar->flags, ar->len, ar->inode ? ar->inode->i_ino : 0,
+                  (unsigned long long) ar->logical,
+                  (unsigned long long) ar->goal,
+                  (unsigned long long) ar->lleft,
+                  (unsigned long long) ar->lright,
+                  (unsigned long long) ar->pleft,
+                  (unsigned long long) ar->pright);
 
        return block;
 }
@@ -4748,6 +4810,10 @@ void ext4_mb_free_blocks(handle_t *handle, struct inode *inode,
        }
 
        ext4_debug("freeing block %lu\n", block);
+       trace_mark(ext4_free_blocks,
+                  "dev %s block %llu count %lu metadata %d ino %lu",
+                  sb->s_id, (unsigned long long) block, count, metadata,
+                  inode ? inode->i_ino : 0);
 
        ac = kmem_cache_alloc(ext4_ac_cachep, GFP_NOFS);
        if (ac) {