]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
et4: allow zeroout when doing written to unwritten split
authorOjaswin Mujoo <ojaswin@linux.ibm.com>
Fri, 23 Jan 2026 06:25:39 +0000 (11:55 +0530)
committerTheodore Ts'o <tytso@mit.edu>
Fri, 23 Jan 2026 21:50:11 +0000 (16:50 -0500)
Currently, when we are doing an extent split and convert operation of
written to unwritten extent (example, as done by ZERO_RANGE), we don't
allow the zeroout fallback in case the extent tree manipulation fails.
This is mostly because zeroout might take unsually long and the fact that
this code path is more tolerant to failures than endio.

Since we have zeroout machinery in place, we might as well use it hence
lift this restriction. To mitigate zeroout taking too long respect the
max zeroout limit here so that the operation finishes relatively fast.

Also, add kunit tests for this case.

Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Zhang Yi <yi.zhang@huawei.com>
Signed-off-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
Link: https://patch.msgid.link/1c3349020b8e098a63f293b84bc8a9b56011cef4.1769149131.git.ojaswin@linux.ibm.com
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
fs/ext4/extents-test.c
fs/ext4/extents.c

index 3176bf7686b5fe07f732be3b2f360c51bc584aa9..4879e68e465de6a8ee9f677099776cbfaa000572 100644 (file)
@@ -613,11 +613,57 @@ static const struct kunit_ext_test_param test_split_convert_params[] = {
                               .is_unwrit = 0 } },
          .is_zeroout_test = 1,
          .nr_exp_data_segs = 3,
+         .exp_data_state = { { .exp_char = 0, .off_blk = 0, .len_blk = 1 },
+                             { .exp_char = 'X', .off_blk = 1, .len_blk = EXT_DATA_LEN - 2 },
+                             { .exp_char = 0, .off_blk = EXT_DATA_LEN - 1, .len_blk = 1 } } },
+
+       /* writ to unwrit splits */
+       { .desc = "split writ extent to 2 extents and convert 1st half unwrit (zeroout)",
+         .type = TEST_SPLIT_CONVERT,
+         .is_unwrit_at_start = 0,
+         .split_flags = EXT4_GET_BLOCKS_CONVERT_UNWRITTEN,
+         .split_map = { .m_lblk = EXT_DATA_LBLK, .m_len = 1 },
+         .nr_exp_ext = 1,
+         .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK,
+                              .ex_len = EXT_DATA_LEN,
+                              .is_unwrit = 0 } },
+         .is_zeroout_test = 1,
+         .nr_exp_data_segs = 2,
          .exp_data_state = { { .exp_char = 0, .off_blk = 0, .len_blk = 1 },
                              { .exp_char = 'X',
                                .off_blk = 1,
-                               .len_blk = EXT_DATA_LEN - 2 },
+                               .len_blk = EXT_DATA_LEN - 1 } } },
+       { .desc = "split writ extent to 2 extents and convert 2nd half unwrit (zeroout)",
+         .type = TEST_SPLIT_CONVERT,
+         .is_unwrit_at_start = 0,
+         .split_flags = EXT4_GET_BLOCKS_CONVERT_UNWRITTEN,
+         .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 1 },
+         .nr_exp_ext = 1,
+         .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK,
+                              .ex_len = EXT_DATA_LEN,
+                              .is_unwrit = 0 } },
+         .is_zeroout_test = 1,
+         .nr_exp_data_segs = 2,
+         .exp_data_state = { { .exp_char = 'X', .off_blk = 0, .len_blk = 1 },
+                             { .exp_char = 0,
+                               .off_blk = 1,
+                               .len_blk = EXT_DATA_LEN - 1 } } },
+       { .desc = "split writ extent to 3 extents and convert 2nd half unwrit (zeroout)",
+         .type = TEST_SPLIT_CONVERT,
+         .is_unwrit_at_start = 0,
+         .split_flags = EXT4_GET_BLOCKS_CONVERT_UNWRITTEN,
+         .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 2 },
+         .nr_exp_ext = 1,
+         .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK,
+                              .ex_len = EXT_DATA_LEN,
+                              .is_unwrit = 0 } },
+         .is_zeroout_test = 1,
+         .nr_exp_data_segs = 3,
+         .exp_data_state = { { .exp_char = 'X', .off_blk = 0, .len_blk = 1 },
                              { .exp_char = 0,
+                               .off_blk = 1,
+                               .len_blk = EXT_DATA_LEN - 2 },
+                             { .exp_char = 'X',
                                .off_blk = EXT_DATA_LEN - 1,
                                .len_blk = 1 } } },
 };
@@ -667,6 +713,56 @@ static const struct kunit_ext_test_param test_convert_initialized_params[] = {
                               .ex_len = 1,
                               .is_unwrit = 0 } },
          .is_zeroout_test = 0 },
+
+       /* writ to unwrit splits (zeroout) */
+       { .desc = "split writ extent to 2 extents and convert 1st half unwrit (zeroout)",
+         .type = TEST_CREATE_BLOCKS,
+         .is_unwrit_at_start = 0,
+         .split_flags = EXT4_GET_BLOCKS_CONVERT_UNWRITTEN,
+         .split_map = { .m_lblk = EXT_DATA_LBLK, .m_len = 1 },
+         .nr_exp_ext = 1,
+         .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK,
+                              .ex_len = EXT_DATA_LEN,
+                              .is_unwrit = 0 } },
+         .is_zeroout_test = 1,
+         .nr_exp_data_segs = 2,
+         .exp_data_state = { { .exp_char = 0, .off_blk = 0, .len_blk = 1 },
+                             { .exp_char = 'X',
+                               .off_blk = 1,
+                               .len_blk = EXT_DATA_LEN - 1 } } },
+       { .desc = "split writ extent to 2 extents and convert 2nd half unwrit (zeroout)",
+         .type = TEST_CREATE_BLOCKS,
+         .is_unwrit_at_start = 0,
+         .split_flags = EXT4_GET_BLOCKS_CONVERT_UNWRITTEN,
+         .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 1 },
+         .nr_exp_ext = 1,
+         .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK,
+                              .ex_len = EXT_DATA_LEN,
+                              .is_unwrit = 0 } },
+         .is_zeroout_test = 1,
+         .nr_exp_data_segs = 2,
+         .exp_data_state = { { .exp_char = 'X', .off_blk = 0, .len_blk = 1 },
+                             { .exp_char = 0,
+                               .off_blk = 1,
+                               .len_blk = EXT_DATA_LEN - 1 } } },
+       { .desc = "split writ extent to 3 extents and convert 2nd half unwrit (zeroout)",
+         .type = TEST_CREATE_BLOCKS,
+         .is_unwrit_at_start = 0,
+         .split_flags = EXT4_GET_BLOCKS_CONVERT_UNWRITTEN,
+         .split_map = { .m_lblk = EXT_DATA_LBLK + 1, .m_len = EXT_DATA_LEN - 2 },
+         .nr_exp_ext = 1,
+         .exp_ext_state = { { .ex_lblk = EXT_DATA_LBLK,
+                              .ex_len = EXT_DATA_LEN,
+                              .is_unwrit = 0 } },
+         .is_zeroout_test = 1,
+         .nr_exp_data_segs = 3,
+         .exp_data_state = { { .exp_char = 'X', .off_blk = 0, .len_blk = 1 },
+                             { .exp_char = 0,
+                               .off_blk = 1,
+                               .len_blk = EXT_DATA_LEN - 2 },
+                             { .exp_char = 'X',
+                               .off_blk = EXT_DATA_LEN - 1,
+                               .len_blk = 1 } } },
 };
 
 /* Tests to trigger ext4_ext_map_blocks() -> ext4_ext_handle_unwritten_exntents() */
index 14f38b3cda27ca33408bc684778555e943b05a62..3630b27e4fd7b901e9f63d03b8a0d7a551527ab5 100644 (file)
@@ -3462,6 +3462,15 @@ try_zeroout:
        if (!(split_flag & EXT4_EXT_MAY_ZEROOUT))
                goto out_orig_err;
 
+       if (flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN) {
+               int max_zeroout_blks =
+                       EXT4_SB(inode->i_sb)->s_extent_max_zeroout_kb >>
+                       (inode->i_sb->s_blocksize_bits - 10);
+
+               if (map->m_len > max_zeroout_blks)
+                       goto out_orig_err;
+       }
+
        path = ext4_find_extent(inode, map->m_lblk, NULL, flags);
        if (IS_ERR(path))
                goto out_orig_err;
@@ -3811,15 +3820,10 @@ static struct ext4_ext_path *ext4_split_convert_extents(handle_t *handle,
                goto convert;
 
        /*
-        * We don't use zeroout fallback for written to unwritten conversion as
-        * it is not as critical as endio and it might take unusually long.
-        * Also, it is only safe to convert extent to initialized via explicit
+        * It is only safe to convert extent to initialized via explicit
         * zeroout only if extent is fully inside i_size or new_size.
         */
-       if (!(flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN))
-               split_flag |= ee_block + ee_len <= eof_block ?
-                                     EXT4_EXT_MAY_ZEROOUT :
-                                     0;
+       split_flag |= ee_block + ee_len <= eof_block ? EXT4_EXT_MAY_ZEROOUT : 0;
 
        /*
         * pass SPLIT_NOMERGE explicitly so we don't end up merging extents we
@@ -3941,7 +3945,20 @@ convert_initialized_extent(handle_t *handle, struct inode *inode,
 
        ext4_update_inode_fsync_trans(handle, inode, 1);
 
-       map->m_flags |= EXT4_MAP_UNWRITTEN;
+       /*
+        * The extent might be initialized in case of zeroout.
+        */
+       path = ext4_find_extent(inode, map->m_lblk, path, flags);
+       if (IS_ERR(path))
+               return path;
+
+       depth = ext_depth(inode);
+       ex = path[depth].p_ext;
+
+       if (ext4_ext_is_unwritten(ex))
+               map->m_flags |= EXT4_MAP_UNWRITTEN;
+       else
+               map->m_flags |= EXT4_MAP_MAPPED;
        if (*allocated > map->m_len)
                *allocated = map->m_len;
        map->m_len = *allocated;