Fix missing parameters to ext4_warning call
[ext4-patch-queue.git] / fix-data-corruption-caused-by-unwriten-and-delayed-extents
blob624a1a8e8c98672fdc70913df2fffdd1f3e61b79
1 ext4: fix data corruption caused by unwritten and delayed extents
3 From: Lukas Czerner <lczerner@redhat.com>
5 Currently it is possible to lose whole file system block worth of data
6 when we hit the specific interaction with unwritten and delayed extents
7 in status extent tree.
9 The problem is that when we insert delayed extent into extent status
10 tree the only way to get rid of it is when we write out delayed buffer.
11 However there is a limitation in the extent status tree implementation
12 so that when inserting unwritten extent should there be even a single
13 delayed block the whole unwritten extent would be marked as delayed.
15 At this point, there is no way to get rid of the delayed extents,
16 because there are no delayed buffers to write out. So when a we write
17 into said unwritten extent we will convert it to written, but it still
18 remains delayed.
20 When we try to write into that block later ext4_da_map_blocks() will set
21 the buffer new and delayed and map it to invalid block which causes
22 the rest of the block to be zeroed loosing already written data.
24 For now we can fix this by simply not allowing to set delayed status on
25 written extent in the extent status tree. Also add WARN_ON() to make
26 sure that we notice if this happens in the future.
28 This problem can be easily reproduced by running the following xfs_io.
30 xfs_io -f -c "pwrite -S 0xaa 4096 2048" \
31           -c "falloc 0 131072" \
32           -c "pwrite -S 0xbb 65536 2048" \
33           -c "fsync" /mnt/test/fff
35 echo 3 > /proc/sys/vm/drop_caches
36 xfs_io -c "pwrite -S 0xdd 67584 2048" /mnt/test/fff
38 This can be theoretically also reproduced by at random by running fsx,
39 but it's not very reliable, though on machines with bigger page size
40 (like ppc) this can be seen more often (especially xfstest generic/127)
42 Signed-off-by: Lukas Czerner <lczerner@redhat.com>
43 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
44 Cc: stable@vger.kernel.org
45 ---
46  fs/ext4/extents_status.c | 8 ++++++++
47  fs/ext4/inode.c          | 2 ++
48  2 files changed, 10 insertions(+)
50 diff --git a/fs/ext4/extents_status.c b/fs/ext4/extents_status.c
51 index d33d5a6..26724ae 100644
52 --- a/fs/ext4/extents_status.c
53 +++ b/fs/ext4/extents_status.c
54 @@ -703,6 +703,14 @@ int ext4_es_insert_extent(struct inode *inode, ext4_lblk_t lblk,
56         BUG_ON(end < lblk);
58 +       if ((status & EXTENT_STATUS_DELAYED) &&
59 +           (status & EXTENT_STATUS_WRITTEN)) {
60 +               ext4_warning(inode->i_sb, "Inserting extent [%u/%u] as "
61 +                               " delayed and written which can potentially "
62 +                               " cause data loss.\n", lblk, len);
63 +               WARN_ON(1);
64 +       }
66         newes.es_lblk = lblk;
67         newes.es_len = len;
68         ext4_es_store_pblock_status(&newes, pblk, status);
69 diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
70 index f6b35d8..4415cea 100644
71 --- a/fs/ext4/inode.c
72 +++ b/fs/ext4/inode.c
73 @@ -532,6 +532,7 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
74                 status = map->m_flags & EXT4_MAP_UNWRITTEN ?
75                                 EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN;
76                 if (!(flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE) &&
77 +                   !(status & EXTENT_STATUS_WRITTEN) &&
78                     ext4_find_delalloc_range(inode, map->m_lblk,
79                                              map->m_lblk + map->m_len - 1))
80                         status |= EXTENT_STATUS_DELAYED;
81 @@ -636,6 +637,7 @@ found:
82                 status = map->m_flags & EXT4_MAP_UNWRITTEN ?
83                                 EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN;
84                 if (!(flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE) &&
85 +                   !(status & EXTENT_STATUS_WRITTEN) &&
86                     ext4_find_delalloc_range(inode, map->m_lblk,
87                                              map->m_lblk + map->m_len - 1))
88                         status |= EXTENT_STATUS_DELAYED;