Commit a26e8c9f authored by Josef Bacik's avatar Josef Bacik Committed by Chris Mason

Btrfs: don't clear uptodate if the eb is under IO

So I have an awful exercise script that will run snapshot, balance and
send/receive in parallel.  This sometimes would crash spectacularly and when it
came back up the fs would be completely hosed.  Turns out this is because of a
bad interaction of balance and send/receive.  Send will hold onto its entire
path for the whole send, but its blocks could get relocated out from underneath
it, and because it doesn't old tree locks theres nothing to keep this from
happening.  So it will go to read in a slot with an old transid, and we could
have re-allocated this block for something else and it could have a completely
different transid.  But because we think it is invalid we clear uptodate and
re-read in the block.  If we do this before we actually write out the new block
we could write back stale data to the fs, and boom we're screwed.

Now we definitely need to fix this disconnect between send and balance, but we
really really need to not allow ourselves to accidently read in stale data over
new data.  So make sure we check if the extent buffer is not under io before
clearing uptodate, this will kick back EIO to the caller instead of reading in
stale data and keep us from corrupting the fs.  Thanks,
Signed-off-by: default avatarJosef Bacik <jbacik@fb.com>
Signed-off-by: default avatarChris Mason <clm@fb.com>
parent 573a0755
...@@ -329,6 +329,8 @@ static int verify_parent_transid(struct extent_io_tree *io_tree, ...@@ -329,6 +329,8 @@ static int verify_parent_transid(struct extent_io_tree *io_tree,
{ {
struct extent_state *cached_state = NULL; struct extent_state *cached_state = NULL;
int ret; int ret;
bool need_lock = (current->journal_info ==
(void *)BTRFS_SEND_TRANS_STUB);
if (!parent_transid || btrfs_header_generation(eb) == parent_transid) if (!parent_transid || btrfs_header_generation(eb) == parent_transid)
return 0; return 0;
...@@ -336,6 +338,11 @@ static int verify_parent_transid(struct extent_io_tree *io_tree, ...@@ -336,6 +338,11 @@ static int verify_parent_transid(struct extent_io_tree *io_tree,
if (atomic) if (atomic)
return -EAGAIN; return -EAGAIN;
if (need_lock) {
btrfs_tree_read_lock(eb);
btrfs_set_lock_blocking_rw(eb, BTRFS_READ_LOCK);
}
lock_extent_bits(io_tree, eb->start, eb->start + eb->len - 1, lock_extent_bits(io_tree, eb->start, eb->start + eb->len - 1,
0, &cached_state); 0, &cached_state);
if (extent_buffer_uptodate(eb) && if (extent_buffer_uptodate(eb) &&
...@@ -347,10 +354,21 @@ static int verify_parent_transid(struct extent_io_tree *io_tree, ...@@ -347,10 +354,21 @@ static int verify_parent_transid(struct extent_io_tree *io_tree,
"found %llu\n", "found %llu\n",
eb->start, parent_transid, btrfs_header_generation(eb)); eb->start, parent_transid, btrfs_header_generation(eb));
ret = 1; ret = 1;
/*
* Things reading via commit roots that don't have normal protection,
* like send, can have a really old block in cache that may point at a
* block that has been free'd and re-allocated. So don't clear uptodate
* if we find an eb that is under IO (dirty/writeback) because we could
* end up reading in the stale data and then writing it back out and
* making everybody very sad.
*/
if (!extent_buffer_under_io(eb))
clear_extent_buffer_uptodate(eb); clear_extent_buffer_uptodate(eb);
out: out:
unlock_extent_cached(io_tree, eb->start, eb->start + eb->len - 1, unlock_extent_cached(io_tree, eb->start, eb->start + eb->len - 1,
&cached_state, GFP_NOFS); &cached_state, GFP_NOFS);
btrfs_tree_read_unlock_blocking(eb);
return ret; return ret;
} }
......
...@@ -4315,7 +4315,7 @@ static void __free_extent_buffer(struct extent_buffer *eb) ...@@ -4315,7 +4315,7 @@ static void __free_extent_buffer(struct extent_buffer *eb)
kmem_cache_free(extent_buffer_cache, eb); kmem_cache_free(extent_buffer_cache, eb);
} }
static int extent_buffer_under_io(struct extent_buffer *eb) int extent_buffer_under_io(struct extent_buffer *eb)
{ {
return (atomic_read(&eb->io_pages) || return (atomic_read(&eb->io_pages) ||
test_bit(EXTENT_BUFFER_WRITEBACK, &eb->bflags) || test_bit(EXTENT_BUFFER_WRITEBACK, &eb->bflags) ||
......
...@@ -320,6 +320,7 @@ int set_extent_buffer_dirty(struct extent_buffer *eb); ...@@ -320,6 +320,7 @@ int set_extent_buffer_dirty(struct extent_buffer *eb);
int set_extent_buffer_uptodate(struct extent_buffer *eb); int set_extent_buffer_uptodate(struct extent_buffer *eb);
int clear_extent_buffer_uptodate(struct extent_buffer *eb); int clear_extent_buffer_uptodate(struct extent_buffer *eb);
int extent_buffer_uptodate(struct extent_buffer *eb); int extent_buffer_uptodate(struct extent_buffer *eb);
int extent_buffer_under_io(struct extent_buffer *eb);
int map_private_extent_buffer(struct extent_buffer *eb, unsigned long offset, int map_private_extent_buffer(struct extent_buffer *eb, unsigned long offset,
unsigned long min_len, char **map, unsigned long min_len, char **map,
unsigned long *map_start, unsigned long *map_start,
......
...@@ -5718,7 +5718,9 @@ long btrfs_ioctl_send(struct file *mnt_file, void __user *arg_) ...@@ -5718,7 +5718,9 @@ long btrfs_ioctl_send(struct file *mnt_file, void __user *arg_)
NULL); NULL);
sort_clone_roots = 1; sort_clone_roots = 1;
current->journal_info = (void *)BTRFS_SEND_TRANS_STUB;
ret = send_subvol(sctx); ret = send_subvol(sctx);
current->journal_info = NULL;
if (ret < 0) if (ret < 0)
goto out; goto out;
......
...@@ -375,7 +375,8 @@ start_transaction(struct btrfs_root *root, u64 num_items, unsigned int type, ...@@ -375,7 +375,8 @@ start_transaction(struct btrfs_root *root, u64 num_items, unsigned int type,
if (test_bit(BTRFS_FS_STATE_ERROR, &root->fs_info->fs_state)) if (test_bit(BTRFS_FS_STATE_ERROR, &root->fs_info->fs_state))
return ERR_PTR(-EROFS); return ERR_PTR(-EROFS);
if (current->journal_info) { if (current->journal_info &&
current->journal_info != (void *)BTRFS_SEND_TRANS_STUB) {
WARN_ON(type & TRANS_EXTWRITERS); WARN_ON(type & TRANS_EXTWRITERS);
h = current->journal_info; h = current->journal_info;
h->use_count++; h->use_count++;
......
...@@ -78,6 +78,8 @@ struct btrfs_transaction { ...@@ -78,6 +78,8 @@ struct btrfs_transaction {
#define TRANS_EXTWRITERS (__TRANS_USERSPACE | __TRANS_START | \ #define TRANS_EXTWRITERS (__TRANS_USERSPACE | __TRANS_START | \
__TRANS_ATTACH) __TRANS_ATTACH)
#define BTRFS_SEND_TRANS_STUB 1
struct btrfs_trans_handle { struct btrfs_trans_handle {
u64 transid; u64 transid;
u64 bytes_reserved; u64 bytes_reserved;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment