ext4: teach ext4_ext_truncate() about the bigalloc feature
[deliverable/linux.git] / fs / ext4 / extents.c
index bd42ab29efec829a03d299e77606674990d5b613..cd4479c080318686a0446bf8568895d5abb8fd0f 100644 (file)
@@ -2202,14 +2202,39 @@ int ext4_ext_index_trans_blocks(struct inode *inode, int nrblocks, int chunk)
 }
 
 static int ext4_remove_blocks(handle_t *handle, struct inode *inode,
-                               struct ext4_extent *ex,
-                               ext4_lblk_t from, ext4_lblk_t to)
+                             struct ext4_extent *ex,
+                             ext4_fsblk_t *partial_cluster,
+                             ext4_lblk_t from, ext4_lblk_t to)
 {
+       struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
        unsigned short ee_len =  ext4_ext_get_actual_len(ex);
+       ext4_fsblk_t pblk;
        int flags = EXT4_FREE_BLOCKS_FORGET;
 
        if (S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode))
                flags |= EXT4_FREE_BLOCKS_METADATA;
+       /*
+        * For bigalloc file systems, we never free a partial cluster
+        * at the beginning of the extent.  Instead, we make a note
+        * that we tried freeing the cluster, and check to see if we
+        * need to free it on a subsequent call to ext4_remove_blocks,
+        * or at the end of the ext4_truncate() operation.
+        */
+       flags |= EXT4_FREE_BLOCKS_NOFREE_FIRST_CLUSTER;
+
+       /*
+        * If we have a partial cluster, and it's different from the
+        * cluster of the last block, we need to explicitly free the
+        * partial cluster here.
+        */
+       pblk = ext4_ext_pblock(ex) + ee_len - 1;
+       if (*partial_cluster && (EXT4_B2C(sbi, pblk) != *partial_cluster)) {
+               ext4_free_blocks(handle, inode, NULL,
+                                EXT4_C2B(sbi, *partial_cluster),
+                                sbi->s_cluster_ratio, flags);
+               *partial_cluster = 0;
+       }
+
 #ifdef EXTENTS_STATS
        {
                struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
@@ -2229,12 +2254,24 @@ static int ext4_remove_blocks(handle_t *handle, struct inode *inode,
            && to == le32_to_cpu(ex->ee_block) + ee_len - 1) {
                /* tail removal */
                ext4_lblk_t num;
-               ext4_fsblk_t start;
 
                num = le32_to_cpu(ex->ee_block) + ee_len - from;
-               start = ext4_ext_pblock(ex) + ee_len - num;
-               ext_debug("free last %u blocks starting %llu\n", num, start);
-               ext4_free_blocks(handle, inode, NULL, start, num, flags);
+               pblk = ext4_ext_pblock(ex) + ee_len - num;
+               ext_debug("free last %u blocks starting %llu\n", num, pblk);
+               ext4_free_blocks(handle, inode, NULL, pblk, num, flags);
+               /*
+                * If the block range to be freed didn't start at the
+                * beginning of a cluster, and we removed the entire
+                * extent, save the partial cluster here, since we
+                * might need to delete if we determine that the
+                * truncate operation has removed all of the blocks in
+                * the cluster.
+                */
+               if (pblk & (sbi->s_cluster_ratio - 1) &&
+                   (ee_len == num))
+                       *partial_cluster = EXT4_B2C(sbi, pblk);
+               else
+                       *partial_cluster = 0;
        } else if (from == le32_to_cpu(ex->ee_block)
                   && to <= le32_to_cpu(ex->ee_block) + ee_len - 1) {
                /* head removal */
@@ -2269,9 +2306,10 @@ static int ext4_remove_blocks(handle_t *handle, struct inode *inode,
  */
 static int
 ext4_ext_rm_leaf(handle_t *handle, struct inode *inode,
-               struct ext4_ext_path *path, ext4_lblk_t start,
-               ext4_lblk_t end)
+                struct ext4_ext_path *path, ext4_fsblk_t *partial_cluster,
+                ext4_lblk_t start, ext4_lblk_t end)
 {
+       struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
        int err = 0, correct_index = 0;
        int depth = ext_depth(inode), credits;
        struct ext4_extent_header *eh;
@@ -2423,7 +2461,8 @@ ext4_ext_rm_leaf(handle_t *handle, struct inode *inode,
                if (err)
                        goto out;
 
-               err = ext4_remove_blocks(handle, inode, ex, a, b);
+               err = ext4_remove_blocks(handle, inode, ex, partial_cluster,
+                                        a, b);
                if (err)
                        goto out;
 
@@ -2471,7 +2510,8 @@ ext4_ext_rm_leaf(handle_t *handle, struct inode *inode,
                                        sizeof(struct ext4_extent));
                        }
                        le16_add_cpu(&eh->eh_entries, -1);
-               }
+               } else
+                       *partial_cluster = 0;
 
                ext_debug("new extent: %u:%u:%llu\n", block, num,
                                ext4_ext_pblock(ex));
@@ -2483,6 +2523,25 @@ ext4_ext_rm_leaf(handle_t *handle, struct inode *inode,
        if (correct_index && eh->eh_entries)
                err = ext4_ext_correct_indexes(handle, inode, path);
 
+       /*
+        * If there is still a entry in the leaf node, check to see if
+        * it references the partial cluster.  This is the only place
+        * where it could; if it doesn't, we can free the cluster.
+        */
+       if (*partial_cluster && ex >= EXT_FIRST_EXTENT(eh) &&
+           (EXT4_B2C(sbi, ext4_ext_pblock(ex) + ex_ee_len - 1) !=
+            *partial_cluster)) {
+               int flags = EXT4_FREE_BLOCKS_FORGET;
+
+               if (S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode))
+                       flags |= EXT4_FREE_BLOCKS_METADATA;
+
+               ext4_free_blocks(handle, inode, NULL,
+                                EXT4_C2B(sbi, *partial_cluster),
+                                sbi->s_cluster_ratio, flags);
+               *partial_cluster = 0;
+       }
+
        /* if this leaf is free, then we should
         * remove it from index block above */
        if (err == 0 && eh->eh_entries == 0 && path[depth].p_bh != NULL)
@@ -2518,6 +2577,7 @@ static int ext4_ext_remove_space(struct inode *inode, ext4_lblk_t start)
        struct super_block *sb = inode->i_sb;
        int depth = ext_depth(inode);
        struct ext4_ext_path *path;
+       ext4_fsblk_t partial_cluster = 0;
        handle_t *handle;
        int i, err;
 
@@ -2553,7 +2613,8 @@ again:
                if (i == depth) {
                        /* this is leaf block */
                        err = ext4_ext_rm_leaf(handle, inode, path,
-                                       start, EXT_MAX_BLOCKS - 1);
+                                              &partial_cluster, start,
+                                              EXT_MAX_BLOCKS - 1);
                        /* root level has p_bh == NULL, brelse() eats this */
                        brelse(path[i].p_bh);
                        path[i].p_bh = NULL;
@@ -3495,6 +3556,8 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
                ee_len = ext4_ext_get_actual_len(ex);
                /* if found extent covers block, simply return it */
                if (in_range(map->m_lblk, ee_block, ee_len)) {
+                       ext4_fsblk_t partial_cluster = 0;
+
                        newblock = map->m_lblk - ee_block + ee_start;
                        /* number of remaining blocks in the extent */
                        allocated = ee_len - (map->m_lblk - ee_block);
@@ -3578,7 +3641,8 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
                        ext4_ext_invalidate_cache(inode);
 
                        err = ext4_ext_rm_leaf(handle, inode, path,
-                               map->m_lblk, map->m_lblk + punched_out);
+                                              &partial_cluster, map->m_lblk,
+                                              map->m_lblk + punched_out);
 
                        if (!err && path->p_hdr->eh_entries == 0) {
                                /*
This page took 0.040386 seconds and 5 git commands to generate.