add patch always-load-checksum-driver
[ext4-patch-queue.git] / update-i_disksize-if-direct-write-past-ondisk-size
blob190b8e1d1679e067b7ab6a7db50fd24522fd4155
1 ext4: update i_disksize if direct write past ondisk size
3 From: Eryu Guan <guaneryu@gmail.com>
5 Currently in ext4 direct write path, we update i_disksize only when
6 new eof is greater than i_size, and don't update it even when new
7 eof is greater than i_disksize but less than i_size. This doesn't
8 work well with delalloc buffer write, which updates i_size and
9 i_disksize only when delalloc blocks are resolved (at writeback
10 time), the i_disksize from direct write can be lost if a previous
11 buffer write succeeded at write time but failed at writeback time,
12 then results in corrupted ondisk inode size.
14 Consider this case, first buffer write 4k data to a new file at
15 offset 16k with delayed allocation, then direct write 4k data to the
16 same file at offset 4k before delalloc blocks are resolved, which
17 doesn't update i_disksize because it writes within i_size(20k), but
18 the extent tree metadata has been committed in journal. Then
19 writeback of the delalloc blocks fails (due to device error etc.),
20 and i_size/i_disksize from buffer write can't be written to disk
21 (still zero). A subsequent umount/mount cycle recovers journal and
22 writes extent tree metadata from direct write to disk, but with
23 i_disksize being zero.
25 Fix it by updating i_disksize too in direct write path when new eof
26 is greater than i_disksize but less than i_size, so i_disksize is
27 always consistent with direct write.
29 This fixes occasional i_size corruption in fstests generic/475.
31 Signed-off-by: Eryu Guan <guaneryu@gmail.com>
32 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
33 ---
35 v2:
36 - basically no change since v1, just fix the locking issue first, and
37   reintroduce the "ei" definition in this patch.
39 I've tested this patchset by looping generic/475 200 times without
40 hitting a corruption, usually it fails within 5 iterations for me. Also
41 tested by full fstests runs on ext2_4k, ext3_2k, ext4_1k configurations
42 and dio tests from LTP, all results looked good.
44  fs/ext4/inode.c | 8 +++++---
45  1 file changed, 5 insertions(+), 3 deletions(-)
47 diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
48 index bff44b4a0783..9acac476c15c 100644
49 --- a/fs/ext4/inode.c
50 +++ b/fs/ext4/inode.c
51 @@ -3658,6 +3658,7 @@ static ssize_t ext4_direct_IO_write(struct kiocb *iocb, struct iov_iter *iter)
52  {
53         struct file *file = iocb->ki_filp;
54         struct inode *inode = file->f_mapping->host;
55 +       struct ext4_inode_info *ei = EXT4_I(inode);
56         ssize_t ret;
57         loff_t offset = iocb->ki_pos;
58         size_t count = iov_iter_count(iter);
59 @@ -3668,7 +3669,7 @@ static ssize_t ext4_direct_IO_write(struct kiocb *iocb, struct iov_iter *iter)
60         int orphan = 0;
61         handle_t *handle;
63 -       if (final_size > inode->i_size) {
64 +       if (final_size > inode->i_size || final_size > ei->i_disksize) {
65                 /* Credits for sb + inode write */
66                 handle = ext4_journal_start(inode, EXT4_HT_INODE, 2);
67                 if (IS_ERR(handle)) {
68 @@ -3788,9 +3789,10 @@ static ssize_t ext4_direct_IO_write(struct kiocb *iocb, struct iov_iter *iter)
69                         ext4_orphan_del(handle, inode);
70                 if (ret > 0) {
71                         loff_t end = offset + ret;
72 -                       if (end > inode->i_size) {
73 +                       if (end > inode->i_size || end > ei->i_disksize) {
74                                 ext4_update_i_disksize(inode, end);
75 -                               i_size_write(inode, end);
76 +                               if (end > inode->i_size)
77 +                                       i_size_write(inode, end);
78                                 /*
79                                  * We're going to return a positive `ret'
80                                  * here due to non-zero-length I/O, so there's
81 -- 
82 2.14.3