From: Denis V. Lunev <[email protected]>

The balloon holds file system space that never carries data, but its
blocks are allocated, so ext4_trim_all_free() never discards them, and
inflating the balloon is a plain fallocate() that does not discard
either. Once the balloon is inflated over blocks that held data, e.g.
on an online shrink after files were removed, the device below keeps
them allocated: a ploop or qcow2 image keeps host disk space the
container can never use, and compaction, which is FITRIM, does not give
it back.

Walk the balloon after the free space in ext4_trim_fs() and discard its
unwritten extents that fall into the requested range. Physically
adjacent extents are merged into one discard, otherwise every cut at
the unwritten extent length limit leaves a partly covered cluster of
the device below. The shared i_rwsem keeps fallocate, truncate and
EXT4_IOC_MOVE_EXT away from the balloon meanwhile, so none of its
blocks can be freed and reused under the discard. An unwritten extent
reads as zeroes whatever the device returns, so the discard changes
nothing visible, and repeating it is harmless.

A balloon inflated before this change is released by the next FITRIM.

https://virtuozzo.atlassian.net/browse/VSTOR-153590

Feature: fs/ext4: fast online shrink support
Signed-off-by: Denis V. Lunev <[email protected]>
---
 fs/ext4/mballoc.c | 99 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 98 insertions(+), 1 deletion(-)

diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c
index aaf2c24c514c..8d205efafac9 100644
--- a/fs/ext4/mballoc.c
+++ b/fs/ext4/mballoc.c
@@ -7009,6 +7009,94 @@ ext4_trim_all_free(struct super_block *sb, ext4_group_t 
group,
        return ret;
 }
 
+static int ext4_trim_balloon_run(struct super_block *sb, ext4_fsblk_t pblk,
+                                ext4_fsblk_t len, ext4_fsblk_t start,
+                                ext4_fsblk_t end, ext4_fsblk_t minblocks,
+                                u64 *trimmed)
+{
+       ext4_fsblk_t last = pblk + len - 1;
+
+       if (!len)
+               return 0;
+       if (pblk < start)
+               pblk = start;
+       if (last > end)
+               last = end;
+       if (last < pblk || last - pblk + 1 < minblocks)
+               return 0;
+
+       len = last - pblk + 1;
+       trace_ext4_discard_blocks(sb, pblk, len);
+       *trimmed += len;
+       return sb_issue_discard(sb, pblk, len, GFP_NOFS, 0);
+}
+
+/*
+ * Blocks of the balloon never hold data, but they are allocated, so
+ * ext4_trim_all_free() never discards them. Discard its unwritten
+ * extents, merging physically adjacent ones, so that the device below
+ * can release the space as it does for free blocks.
+ */
+static int ext4_trim_balloon(struct super_block *sb, ext4_fsblk_t start,
+                            ext4_fsblk_t end, ext4_fsblk_t minblocks,
+                            u64 *trimmed)
+{
+       struct ext4_sb_info *sbi = EXT4_SB(sb);
+       ext4_fsblk_t run_pblk = 0, run_len = 0;
+       struct ext4_map_blocks map;
+       struct inode *inode = NULL;
+       ext4_lblk_t lblk, nr_lblks;
+       bool unwritten;
+       int ret = 0, err;
+
+       /* ext4_load_balloon() replaces s_balloon_ino under s_umount */
+       down_read(&sb->s_umount);
+       if (sbi->s_balloon_ino && !sb_rdonly(sb))
+               inode = igrab(sbi->s_balloon_ino);
+       up_read(&sb->s_umount);
+       if (!inode)
+               return 0;
+
+       inode_lock_shared(inode);
+       nr_lblks = min_t(u64, EXT_MAX_BLOCKS,
+                        DIV_ROUND_UP(i_size_read(inode), sb->s_blocksize));
+       for (lblk = 0; lblk < nr_lblks; lblk += map.m_len) {
+               if (ext4_trim_interrupted())
+                       break;
+               cond_resched();
+
+               map.m_lblk = lblk;
+               map.m_len = nr_lblks - lblk;
+               err = ext4_map_blocks(NULL, inode, &map, 0);
+               if (err < 0) {
+                       ret = err;
+                       break;
+               }
+               if (!map.m_len)
+                       break;
+
+               unwritten = err > 0 && (map.m_flags & EXT4_MAP_UNWRITTEN);
+               if (unwritten && run_len && run_pblk + run_len == map.m_pblk) {
+                       run_len += map.m_len;
+                       continue;
+               }
+
+               ret = ext4_trim_balloon_run(sb, run_pblk, run_len, start, end,
+                                           minblocks, trimmed);
+               if (ret)
+                       break;
+               run_pblk = map.m_pblk;
+               run_len = unwritten ? map.m_len : 0;
+       }
+       if (!ret && !ext4_trim_interrupted())
+               ret = ext4_trim_balloon_run(sb, run_pblk, run_len, start, end,
+                                           minblocks, trimmed);
+       inode_unlock_shared(inode);
+       iput(inode);
+
+       return ret;
+}
+
 /**
  * ext4_trim_fs() -- trim ioctl handle function
  * @sb:                        superblock for filesystem
@@ -7027,7 +7115,8 @@ int ext4_trim_fs(struct super_block *sb, struct 
fstrim_range *range)
        struct ext4_group_info *grp;
        ext4_group_t group, first_group, last_group;
        ext4_grpblk_t cnt = 0, first_cluster, last_cluster;
-       uint64_t start, end, minlen, trimmed = 0;
+       uint64_t start, end, minlen, trimmed = 0, balloon_blks = 0;
+       ext4_fsblk_t last_blk;
        ext4_fsblk_t first_data_blk =
                        le32_to_cpu(EXT4_SB(sb)->s_es->s_first_data_block);
        ext4_fsblk_t max_blks = ext4_blocks_count(EXT4_SB(sb)->s_es);
@@ -7061,6 +7150,7 @@ int ext4_trim_fs(struct super_block *sb, struct 
fstrim_range *range)
                                     &first_group, &first_cluster);
        ext4_get_group_no_and_offset(sb, (ext4_fsblk_t) end,
                                     &last_group, &last_cluster);
+       last_blk = end;
 
        /* end now represents the last cluster to discard in this group */
        end = EXT4_CLUSTERS_PER_GROUP(sb) - 1;
@@ -7103,6 +7193,13 @@ int ext4_trim_fs(struct super_block *sb, struct 
fstrim_range *range)
                first_cluster = 0;
        }
 
+       if (!ret && !ext4_trim_interrupted()) {
+               ret = ext4_trim_balloon(sb, start, last_blk,
+                                       EXT4_C2B(EXT4_SB(sb), minlen),
+                                       &balloon_blks);
+               trimmed += EXT4_NUM_B2C(EXT4_SB(sb), balloon_blks);
+       }
+
        if (!ret)
                EXT4_SB(sb)->s_last_trim_minblks = minlen;
 
-- 
2.53.0

_______________________________________________
Devel mailing list
[email protected]
https://lists.openvz.org/mailman/listinfo/devel

Reply via email to