aboutsummaryrefslogtreecommitdiffstats
path: root/fs/ext4
diff options
context:
space:
mode:
authorLukas Czerner <lczerner@redhat.com>2015-05-02 21:36:55 -0400
committerTheodore Ts'o <tytso@mit.edu>2015-05-02 21:36:55 -0400
commitd2dc317d564a46dfc683978a2e5a4f91434e9711 (patch)
tree0873df5d4d1ff8aca67bc3025d7a6a095522c764 /fs/ext4
parent9402bdcacdfedf7219a17e4d93300058a8e2aa4c (diff)
ext4: fix data corruption caused by unwritten and delayed extents
Currently it is possible to lose whole file system block worth of data when we hit the specific interaction with unwritten and delayed extents in status extent tree. The problem is that when we insert delayed extent into extent status tree the only way to get rid of it is when we write out delayed buffer. However there is a limitation in the extent status tree implementation so that when inserting unwritten extent should there be even a single delayed block the whole unwritten extent would be marked as delayed. At this point, there is no way to get rid of the delayed extents, because there are no delayed buffers to write out. So when a we write into said unwritten extent we will convert it to written, but it still remains delayed. When we try to write into that block later ext4_da_map_blocks() will set the buffer new and delayed and map it to invalid block which causes the rest of the block to be zeroed loosing already written data. For now we can fix this by simply not allowing to set delayed status on written extent in the extent status tree. Also add WARN_ON() to make sure that we notice if this happens in the future. This problem can be easily reproduced by running the following xfs_io. xfs_io -f -c "pwrite -S 0xaa 4096 2048" \ -c "falloc 0 131072" \ -c "pwrite -S 0xbb 65536 2048" \ -c "fsync" /mnt/test/fff echo 3 > /proc/sys/vm/drop_caches xfs_io -c "pwrite -S 0xdd 67584 2048" /mnt/test/fff This can be theoretically also reproduced by at random by running fsx, but it's not very reliable, though on machines with bigger page size (like ppc) this can be seen more often (especially xfstest generic/127) Signed-off-by: Lukas Czerner <lczerner@redhat.com> Signed-off-by: Theodore Ts'o <tytso@mit.edu> Cc: stable@vger.kernel.org
Diffstat (limited to 'fs/ext4')
-rw-r--r--fs/ext4/extents_status.c8
-rw-r--r--fs/ext4/inode.c2
2 files changed, 10 insertions, 0 deletions
diff --git a/fs/ext4/extents_status.c b/fs/ext4/extents_status.c
index d33d5a6852b9..26724aeece73 100644
--- a/fs/ext4/extents_status.c
+++ b/fs/ext4/extents_status.c
@@ -703,6 +703,14 @@ int ext4_es_insert_extent(struct inode *inode, ext4_lblk_t lblk,
703 703
704 BUG_ON(end < lblk); 704 BUG_ON(end < lblk);
705 705
706 if ((status & EXTENT_STATUS_DELAYED) &&
707 (status & EXTENT_STATUS_WRITTEN)) {
708 ext4_warning(inode->i_sb, "Inserting extent [%u/%u] as "
709 " delayed and written which can potentially "
710 " cause data loss.\n", lblk, len);
711 WARN_ON(1);
712 }
713
706 newes.es_lblk = lblk; 714 newes.es_lblk = lblk;
707 newes.es_len = len; 715 newes.es_len = len;
708 ext4_es_store_pblock_status(&newes, pblk, status); 716 ext4_es_store_pblock_status(&newes, pblk, status);
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index f6b35d8a4a5b..4415cea85ced 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -532,6 +532,7 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
532 status = map->m_flags & EXT4_MAP_UNWRITTEN ? 532 status = map->m_flags & EXT4_MAP_UNWRITTEN ?
533 EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN; 533 EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN;
534 if (!(flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE) && 534 if (!(flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE) &&
535 !(status & EXTENT_STATUS_WRITTEN) &&
535 ext4_find_delalloc_range(inode, map->m_lblk, 536 ext4_find_delalloc_range(inode, map->m_lblk,
536 map->m_lblk + map->m_len - 1)) 537 map->m_lblk + map->m_len - 1))
537 status |= EXTENT_STATUS_DELAYED; 538 status |= EXTENT_STATUS_DELAYED;
@@ -636,6 +637,7 @@ found:
636 status = map->m_flags & EXT4_MAP_UNWRITTEN ? 637 status = map->m_flags & EXT4_MAP_UNWRITTEN ?
637 EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN; 638 EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN;
638 if (!(flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE) && 639 if (!(flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE) &&
640 !(status & EXTENT_STATUS_WRITTEN) &&
639 ext4_find_delalloc_range(inode, map->m_lblk, 641 ext4_find_delalloc_range(inode, map->m_lblk,
640 map->m_lblk + map->m_len - 1)) 642 map->m_lblk + map->m_len - 1))
641 status |= EXTENT_STATUS_DELAYED; 643 status |= EXTENT_STATUS_DELAYED;