]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ext4: ensure zeroed partial blocks are persisted in SYNC mode
authorZhang Yi <yi.zhang@huawei.com>
Fri, 27 Mar 2026 10:29:35 +0000 (18:29 +0800)
committerTheodore Ts'o <tytso@mit.edu>
Fri, 10 Apr 2026 01:57:52 +0000 (21:57 -0400)
In ext4_zero_range() and ext4_punch_hole(), when operating in SYNC mode
and zeroing a partial block, only data=journal modes guarantee that the
zeroed data is synchronously persisted after the operation completes.
For data=ordered/writeback mode and non-journal modes, this guarantee is
missing.

Introduce a partial_zero parameter to explicitly trigger writeback for
all scenarios where a partial block is zeroed, ensuring the zeroed data
is durably persisted.

Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
Link: https://patch.msgid.link/20260327102939.1095257-10-yi.zhang@huaweicloud.com
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
fs/ext4/ext4.h
fs/ext4/extents.c
fs/ext4/inode.c

index 75559a771a54491a4aa102328af4d8ab637c6e30..56b82d4a15d710967b17d5d5024ed42017cac582 100644 (file)
@@ -3099,7 +3099,7 @@ extern int ext4_meta_trans_blocks(struct inode *inode, int lblocks,
                                  int pextents);
 extern int ext4_block_zero_eof(struct inode *inode, loff_t from, loff_t end);
 extern int ext4_zero_partial_blocks(struct inode *inode, loff_t lstart,
-                                   loff_t length);
+                                   loff_t length, bool *did_zero);
 extern vm_fault_t ext4_page_mkwrite(struct vm_fault *vmf);
 extern qsize_t *ext4_get_reserved_space(struct inode *inode);
 extern int ext4_get_projid(struct inode *inode, kprojid_t *projid);
index 0053e2123fea7068251639f6084cff924bd3438d..00b9860d387570fc8bc5118f3538f2bbf88707e4 100644 (file)
@@ -4702,6 +4702,7 @@ static long ext4_zero_range(struct file *file, loff_t offset,
        loff_t align_start, align_end, new_size = 0;
        loff_t end = offset + len;
        unsigned int blocksize = i_blocksize(inode);
+       bool partial_zeroed = false;
        int ret, flags;
 
        trace_ext4_zero_range(inode, offset, len, mode);
@@ -4757,9 +4758,15 @@ static long ext4_zero_range(struct file *file, loff_t offset,
                return ret;
 
        /* Zero out partial block at the edges of the range */
-       ret = ext4_zero_partial_blocks(inode, offset, len);
+       ret = ext4_zero_partial_blocks(inode, offset, len, &partial_zeroed);
        if (ret)
                return ret;
+       if (((file->f_flags & O_SYNC) || IS_SYNC(inode)) && partial_zeroed) {
+               ret = filemap_write_and_wait_range(inode->i_mapping, offset,
+                                                  end - 1);
+               if (ret)
+                       return ret;
+       }
 
        handle = ext4_journal_start(inode, EXT4_HT_MISC, 1);
        if (IS_ERR(handle)) {
index 54a376ee0717d3aa8d82800da026f495dfa3f01d..cb1365bbb84366f6e36730f9ce6534fa0558480b 100644 (file)
@@ -4257,7 +4257,8 @@ int ext4_block_zero_eof(struct inode *inode, loff_t from, loff_t end)
        return 0;
 }
 
-int ext4_zero_partial_blocks(struct inode *inode, loff_t lstart, loff_t length)
+int ext4_zero_partial_blocks(struct inode *inode, loff_t lstart, loff_t length,
+                            bool *did_zero)
 {
        struct super_block *sb = inode->i_sb;
        unsigned partial_start, partial_end;
@@ -4274,20 +4275,21 @@ int ext4_zero_partial_blocks(struct inode *inode, loff_t lstart, loff_t length)
        /* Handle partial zero within the single block */
        if (start == end &&
            (partial_start || (partial_end != sb->s_blocksize - 1))) {
-               err = ext4_block_zero_range(inode, lstart, length, NULL, NULL);
+               err = ext4_block_zero_range(inode, lstart, length, did_zero,
+                                           NULL);
                return err;
        }
        /* Handle partial zero out on the start of the range */
        if (partial_start) {
                err = ext4_block_zero_range(inode, lstart, sb->s_blocksize,
-                                           NULL, NULL);
+                                           did_zero, NULL);
                if (err)
                        return err;
        }
        /* Handle partial zero out on the end of the range */
        if (partial_end != sb->s_blocksize - 1)
                err = ext4_block_zero_range(inode, byte_end - partial_end,
-                                           partial_end + 1, NULL, NULL);
+                                           partial_end + 1, did_zero, NULL);
        return err;
 }
 
@@ -4436,6 +4438,7 @@ int ext4_punch_hole(struct file *file, loff_t offset, loff_t length)
        loff_t end = offset + length;
        handle_t *handle;
        unsigned int credits;
+       bool partial_zeroed = false;
        int ret;
 
        trace_ext4_punch_hole(inode, offset, length, 0);
@@ -4471,9 +4474,15 @@ int ext4_punch_hole(struct file *file, loff_t offset, loff_t length)
        if (ret)
                return ret;
 
-       ret = ext4_zero_partial_blocks(inode, offset, length);
+       ret = ext4_zero_partial_blocks(inode, offset, length, &partial_zeroed);
        if (ret)
                return ret;
+       if (((file->f_flags & O_SYNC) || IS_SYNC(inode)) && partial_zeroed) {
+               ret = filemap_write_and_wait_range(inode->i_mapping, offset,
+                                                  end - 1);
+               if (ret)
+                       return ret;
+       }
 
        if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
                credits = ext4_chunk_trans_extent(inode, 0);