Commit c2167754 authored by Yan, Zheng's avatar Yan, Zheng Committed by Chris Mason

Btrfs: Fix disk_i_size update corner case

There are some cases file extents are inserted without involving
ordered struct. In these cases, we update disk_i_size directly,
without checking pending ordered extent and DELALLOC bit. This
patch extends btrfs_ordered_update_i_size() to handle these cases.
Signed-off-by: default avatarYan Zheng <zheng.yan@oracle.com>
Signed-off-by: default avatarChris Mason <chris.mason@oracle.com>
parent 920bbbfb
...@@ -44,9 +44,6 @@ struct btrfs_inode { ...@@ -44,9 +44,6 @@ struct btrfs_inode {
*/ */
struct extent_io_tree io_failure_tree; struct extent_io_tree io_failure_tree;
/* held while inesrting or deleting extents from files */
struct mutex extent_mutex;
/* held while logging the inode in tree-log.c */ /* held while logging the inode in tree-log.c */
struct mutex log_mutex; struct mutex log_mutex;
...@@ -166,7 +163,7 @@ static inline struct btrfs_inode *BTRFS_I(struct inode *inode) ...@@ -166,7 +163,7 @@ static inline struct btrfs_inode *BTRFS_I(struct inode *inode)
static inline void btrfs_i_size_write(struct inode *inode, u64 size) static inline void btrfs_i_size_write(struct inode *inode, u64 size)
{ {
inode->i_size = size; i_size_write(inode, size);
BTRFS_I(inode)->disk_i_size = size; BTRFS_I(inode)->disk_i_size = size;
} }
......
...@@ -188,8 +188,18 @@ static noinline int insert_inline_extent(struct btrfs_trans_handle *trans, ...@@ -188,8 +188,18 @@ static noinline int insert_inline_extent(struct btrfs_trans_handle *trans,
btrfs_mark_buffer_dirty(leaf); btrfs_mark_buffer_dirty(leaf);
btrfs_free_path(path); btrfs_free_path(path);
/*
* we're an inline extent, so nobody can
* extend the file past i_size without locking
* a page we already have locked.
*
* We must do any isize and inode updates
* before we unlock the pages. Otherwise we
* could end up racing with unlink.
*/
BTRFS_I(inode)->disk_i_size = inode->i_size; BTRFS_I(inode)->disk_i_size = inode->i_size;
btrfs_update_inode(trans, root, inode); btrfs_update_inode(trans, root, inode);
return 0; return 0;
fail: fail:
btrfs_free_path(path); btrfs_free_path(path);
...@@ -415,7 +425,6 @@ again: ...@@ -415,7 +425,6 @@ again:
start, end, start, end,
total_compressed, pages); total_compressed, pages);
} }
btrfs_end_transaction(trans, root);
if (ret == 0) { if (ret == 0) {
/* /*
* inline extent creation worked, we don't need * inline extent creation worked, we don't need
...@@ -429,9 +438,11 @@ again: ...@@ -429,9 +438,11 @@ again:
EXTENT_CLEAR_DELALLOC | EXTENT_CLEAR_DELALLOC |
EXTENT_CLEAR_ACCOUNTING | EXTENT_CLEAR_ACCOUNTING |
EXTENT_SET_WRITEBACK | EXTENT_END_WRITEBACK); EXTENT_SET_WRITEBACK | EXTENT_END_WRITEBACK);
ret = 0;
btrfs_end_transaction(trans, root);
goto free_pages_out; goto free_pages_out;
} }
btrfs_end_transaction(trans, root);
} }
if (will_compress) { if (will_compress) {
...@@ -542,7 +553,6 @@ static noinline int submit_compressed_extents(struct inode *inode, ...@@ -542,7 +553,6 @@ static noinline int submit_compressed_extents(struct inode *inode,
if (list_empty(&async_cow->extents)) if (list_empty(&async_cow->extents))
return 0; return 0;
trans = btrfs_join_transaction(root, 1);
while (!list_empty(&async_cow->extents)) { while (!list_empty(&async_cow->extents)) {
async_extent = list_entry(async_cow->extents.next, async_extent = list_entry(async_cow->extents.next,
...@@ -589,19 +599,15 @@ retry: ...@@ -589,19 +599,15 @@ retry:
lock_extent(io_tree, async_extent->start, lock_extent(io_tree, async_extent->start,
async_extent->start + async_extent->ram_size - 1, async_extent->start + async_extent->ram_size - 1,
GFP_NOFS); GFP_NOFS);
/*
* here we're doing allocation and writeback of the
* compressed pages
*/
btrfs_drop_extent_cache(inode, async_extent->start,
async_extent->start +
async_extent->ram_size - 1, 0);
trans = btrfs_join_transaction(root, 1);
ret = btrfs_reserve_extent(trans, root, ret = btrfs_reserve_extent(trans, root,
async_extent->compressed_size, async_extent->compressed_size,
async_extent->compressed_size, async_extent->compressed_size,
0, alloc_hint, 0, alloc_hint,
(u64)-1, &ins, 1); (u64)-1, &ins, 1);
btrfs_end_transaction(trans, root);
if (ret) { if (ret) {
int i; int i;
for (i = 0; i < async_extent->nr_pages; i++) { for (i = 0; i < async_extent->nr_pages; i++) {
...@@ -617,6 +623,14 @@ retry: ...@@ -617,6 +623,14 @@ retry:
goto retry; goto retry;
} }
/*
* here we're doing allocation and writeback of the
* compressed pages
*/
btrfs_drop_extent_cache(inode, async_extent->start,
async_extent->start +
async_extent->ram_size - 1, 0);
em = alloc_extent_map(GFP_NOFS); em = alloc_extent_map(GFP_NOFS);
em->start = async_extent->start; em->start = async_extent->start;
em->len = async_extent->ram_size; em->len = async_extent->ram_size;
...@@ -648,8 +662,6 @@ retry: ...@@ -648,8 +662,6 @@ retry:
BTRFS_ORDERED_COMPRESSED); BTRFS_ORDERED_COMPRESSED);
BUG_ON(ret); BUG_ON(ret);
btrfs_end_transaction(trans, root);
/* /*
* clear dirty, set writeback and unlock the pages. * clear dirty, set writeback and unlock the pages.
*/ */
...@@ -671,13 +683,11 @@ retry: ...@@ -671,13 +683,11 @@ retry:
async_extent->nr_pages); async_extent->nr_pages);
BUG_ON(ret); BUG_ON(ret);
trans = btrfs_join_transaction(root, 1);
alloc_hint = ins.objectid + ins.offset; alloc_hint = ins.objectid + ins.offset;
kfree(async_extent); kfree(async_extent);
cond_resched(); cond_resched();
} }
btrfs_end_transaction(trans, root);
return 0; return 0;
} }
...@@ -741,6 +751,7 @@ static noinline int cow_file_range(struct inode *inode, ...@@ -741,6 +751,7 @@ static noinline int cow_file_range(struct inode *inode,
EXTENT_CLEAR_DIRTY | EXTENT_CLEAR_DIRTY |
EXTENT_SET_WRITEBACK | EXTENT_SET_WRITEBACK |
EXTENT_END_WRITEBACK); EXTENT_END_WRITEBACK);
*nr_written = *nr_written + *nr_written = *nr_written +
(end - start + PAGE_CACHE_SIZE) / PAGE_CACHE_SIZE; (end - start + PAGE_CACHE_SIZE) / PAGE_CACHE_SIZE;
*page_started = 1; *page_started = 1;
...@@ -1727,18 +1738,27 @@ static int btrfs_finish_ordered_io(struct inode *inode, u64 start, u64 end) ...@@ -1727,18 +1738,27 @@ static int btrfs_finish_ordered_io(struct inode *inode, u64 start, u64 end)
} }
} }
trans = btrfs_join_transaction(root, 1);
if (!ordered_extent) if (!ordered_extent)
ordered_extent = btrfs_lookup_ordered_extent(inode, start); ordered_extent = btrfs_lookup_ordered_extent(inode, start);
BUG_ON(!ordered_extent); BUG_ON(!ordered_extent);
if (test_bit(BTRFS_ORDERED_NOCOW, &ordered_extent->flags)) if (test_bit(BTRFS_ORDERED_NOCOW, &ordered_extent->flags)) {
goto nocow; BUG_ON(!list_empty(&ordered_extent->list));
ret = btrfs_ordered_update_i_size(inode, 0, ordered_extent);
if (!ret) {
trans = btrfs_join_transaction(root, 1);
ret = btrfs_update_inode(trans, root, inode);
BUG_ON(ret);
btrfs_end_transaction(trans, root);
}
goto out;
}
lock_extent(io_tree, ordered_extent->file_offset, lock_extent(io_tree, ordered_extent->file_offset,
ordered_extent->file_offset + ordered_extent->len - 1, ordered_extent->file_offset + ordered_extent->len - 1,
GFP_NOFS); GFP_NOFS);
trans = btrfs_join_transaction(root, 1);
if (test_bit(BTRFS_ORDERED_COMPRESSED, &ordered_extent->flags)) if (test_bit(BTRFS_ORDERED_COMPRESSED, &ordered_extent->flags))
compressed = 1; compressed = 1;
if (test_bit(BTRFS_ORDERED_PREALLOC, &ordered_extent->flags)) { if (test_bit(BTRFS_ORDERED_PREALLOC, &ordered_extent->flags)) {
...@@ -1765,22 +1785,20 @@ static int btrfs_finish_ordered_io(struct inode *inode, u64 start, u64 end) ...@@ -1765,22 +1785,20 @@ static int btrfs_finish_ordered_io(struct inode *inode, u64 start, u64 end)
unlock_extent(io_tree, ordered_extent->file_offset, unlock_extent(io_tree, ordered_extent->file_offset,
ordered_extent->file_offset + ordered_extent->len - 1, ordered_extent->file_offset + ordered_extent->len - 1,
GFP_NOFS); GFP_NOFS);
nocow:
add_pending_csums(trans, inode, ordered_extent->file_offset, add_pending_csums(trans, inode, ordered_extent->file_offset,
&ordered_extent->list); &ordered_extent->list);
mutex_lock(&BTRFS_I(inode)->extent_mutex); /* this also removes the ordered extent from the tree */
btrfs_ordered_update_i_size(inode, ordered_extent); btrfs_ordered_update_i_size(inode, 0, ordered_extent);
btrfs_update_inode(trans, root, inode); ret = btrfs_update_inode(trans, root, inode);
btrfs_remove_ordered_extent(inode, ordered_extent); BUG_ON(ret);
mutex_unlock(&BTRFS_I(inode)->extent_mutex); btrfs_end_transaction(trans, root);
out:
/* once for us */ /* once for us */
btrfs_put_ordered_extent(ordered_extent); btrfs_put_ordered_extent(ordered_extent);
/* once for the tree */ /* once for the tree */
btrfs_put_ordered_extent(ordered_extent); btrfs_put_ordered_extent(ordered_extent);
btrfs_end_transaction(trans, root);
return 0; return 0;
} }
...@@ -3562,7 +3580,6 @@ static noinline void init_btrfs_i(struct inode *inode) ...@@ -3562,7 +3580,6 @@ static noinline void init_btrfs_i(struct inode *inode)
INIT_LIST_HEAD(&BTRFS_I(inode)->ordered_operations); INIT_LIST_HEAD(&BTRFS_I(inode)->ordered_operations);
RB_CLEAR_NODE(&BTRFS_I(inode)->rb_node); RB_CLEAR_NODE(&BTRFS_I(inode)->rb_node);
btrfs_ordered_inode_tree_init(&BTRFS_I(inode)->ordered_tree); btrfs_ordered_inode_tree_init(&BTRFS_I(inode)->ordered_tree);
mutex_init(&BTRFS_I(inode)->extent_mutex);
mutex_init(&BTRFS_I(inode)->log_mutex); mutex_init(&BTRFS_I(inode)->log_mutex);
} }
......
...@@ -291,16 +291,16 @@ int btrfs_put_ordered_extent(struct btrfs_ordered_extent *entry) ...@@ -291,16 +291,16 @@ int btrfs_put_ordered_extent(struct btrfs_ordered_extent *entry)
/* /*
* remove an ordered extent from the tree. No references are dropped * remove an ordered extent from the tree. No references are dropped
* but, anyone waiting on this extent is woken up. * and you must wake_up entry->wait. You must hold the tree mutex
* while you call this function.
*/ */
int btrfs_remove_ordered_extent(struct inode *inode, static int __btrfs_remove_ordered_extent(struct inode *inode,
struct btrfs_ordered_extent *entry) struct btrfs_ordered_extent *entry)
{ {
struct btrfs_ordered_inode_tree *tree; struct btrfs_ordered_inode_tree *tree;
struct rb_node *node; struct rb_node *node;
tree = &BTRFS_I(inode)->ordered_tree; tree = &BTRFS_I(inode)->ordered_tree;
mutex_lock(&tree->mutex);
node = &entry->rb_node; node = &entry->rb_node;
rb_erase(node, &tree->tree); rb_erase(node, &tree->tree);
tree->last = NULL; tree->last = NULL;
...@@ -326,9 +326,26 @@ int btrfs_remove_ordered_extent(struct inode *inode, ...@@ -326,9 +326,26 @@ int btrfs_remove_ordered_extent(struct inode *inode,
} }
spin_unlock(&BTRFS_I(inode)->root->fs_info->ordered_extent_lock); spin_unlock(&BTRFS_I(inode)->root->fs_info->ordered_extent_lock);
return 0;
}
/*
* remove an ordered extent from the tree. No references are dropped
* but any waiters are woken.
*/
int btrfs_remove_ordered_extent(struct inode *inode,
struct btrfs_ordered_extent *entry)
{
struct btrfs_ordered_inode_tree *tree;
int ret;
tree = &BTRFS_I(inode)->ordered_tree;
mutex_lock(&tree->mutex);
ret = __btrfs_remove_ordered_extent(inode, entry);
mutex_unlock(&tree->mutex); mutex_unlock(&tree->mutex);
wake_up(&entry->wait); wake_up(&entry->wait);
return 0;
return ret;
} }
/* /*
...@@ -589,7 +606,7 @@ out: ...@@ -589,7 +606,7 @@ out:
* After an extent is done, call this to conditionally update the on disk * After an extent is done, call this to conditionally update the on disk
* i_size. i_size is updated to cover any fully written part of the file. * i_size. i_size is updated to cover any fully written part of the file.
*/ */
int btrfs_ordered_update_i_size(struct inode *inode, int btrfs_ordered_update_i_size(struct inode *inode, u64 offset,
struct btrfs_ordered_extent *ordered) struct btrfs_ordered_extent *ordered)
{ {
struct btrfs_ordered_inode_tree *tree = &BTRFS_I(inode)->ordered_tree; struct btrfs_ordered_inode_tree *tree = &BTRFS_I(inode)->ordered_tree;
...@@ -597,18 +614,30 @@ int btrfs_ordered_update_i_size(struct inode *inode, ...@@ -597,18 +614,30 @@ int btrfs_ordered_update_i_size(struct inode *inode,
u64 disk_i_size; u64 disk_i_size;
u64 new_i_size; u64 new_i_size;
u64 i_size_test; u64 i_size_test;
u64 i_size = i_size_read(inode);
struct rb_node *node; struct rb_node *node;
struct rb_node *prev = NULL;
struct btrfs_ordered_extent *test; struct btrfs_ordered_extent *test;
int ret = 1;
if (ordered)
offset = entry_end(ordered);
mutex_lock(&tree->mutex); mutex_lock(&tree->mutex);
disk_i_size = BTRFS_I(inode)->disk_i_size; disk_i_size = BTRFS_I(inode)->disk_i_size;
/* truncate file */
if (disk_i_size > i_size) {
BTRFS_I(inode)->disk_i_size = i_size;
ret = 0;
goto out;
}
/* /*
* if the disk i_size is already at the inode->i_size, or * if the disk i_size is already at the inode->i_size, or
* this ordered extent is inside the disk i_size, we're done * this ordered extent is inside the disk i_size, we're done
*/ */
if (disk_i_size >= inode->i_size || if (disk_i_size == i_size || offset <= disk_i_size) {
ordered->file_offset + ordered->len <= disk_i_size) {
goto out; goto out;
} }
...@@ -616,8 +645,7 @@ int btrfs_ordered_update_i_size(struct inode *inode, ...@@ -616,8 +645,7 @@ int btrfs_ordered_update_i_size(struct inode *inode,
* we can't update the disk_isize if there are delalloc bytes * we can't update the disk_isize if there are delalloc bytes
* between disk_i_size and this ordered extent * between disk_i_size and this ordered extent
*/ */
if (test_range_bit(io_tree, disk_i_size, if (test_range_bit(io_tree, disk_i_size, offset - 1,
ordered->file_offset + ordered->len - 1,
EXTENT_DELALLOC, 0, NULL)) { EXTENT_DELALLOC, 0, NULL)) {
goto out; goto out;
} }
...@@ -626,20 +654,32 @@ int btrfs_ordered_update_i_size(struct inode *inode, ...@@ -626,20 +654,32 @@ int btrfs_ordered_update_i_size(struct inode *inode,
* if we find an ordered extent then we can't update disk i_size * if we find an ordered extent then we can't update disk i_size
* yet * yet
*/ */
node = &ordered->rb_node; if (ordered) {
while (1) { node = rb_prev(&ordered->rb_node);
node = rb_prev(node); } else {
if (!node) prev = tree_search(tree, offset);
break; /*
* we insert file extents without involving ordered struct,
* so there should be no ordered struct cover this offset
*/
if (prev) {
test = rb_entry(prev, struct btrfs_ordered_extent,
rb_node);
BUG_ON(offset_in_entry(test, offset));
}
node = prev;
}
while (node) {
test = rb_entry(node, struct btrfs_ordered_extent, rb_node); test = rb_entry(node, struct btrfs_ordered_extent, rb_node);
if (test->file_offset + test->len <= disk_i_size) if (test->file_offset + test->len <= disk_i_size)
break; break;
if (test->file_offset >= inode->i_size) if (test->file_offset >= i_size)
break; break;
if (test->file_offset >= disk_i_size) if (test->file_offset >= disk_i_size)
goto out; goto out;
node = rb_prev(node);
} }
new_i_size = min_t(u64, entry_end(ordered), i_size_read(inode)); new_i_size = min_t(u64, offset, i_size);
/* /*
* at this point, we know we can safely update i_size to at least * at this point, we know we can safely update i_size to at least
...@@ -647,7 +687,14 @@ int btrfs_ordered_update_i_size(struct inode *inode, ...@@ -647,7 +687,14 @@ int btrfs_ordered_update_i_size(struct inode *inode,
* walk forward and see if ios from higher up in the file have * walk forward and see if ios from higher up in the file have
* finished. * finished.
*/ */
node = rb_next(&ordered->rb_node); if (ordered) {
node = rb_next(&ordered->rb_node);
} else {
if (prev)
node = rb_next(prev);
else
node = rb_first(&tree->tree);
}
i_size_test = 0; i_size_test = 0;
if (node) { if (node) {
/* /*
...@@ -655,10 +702,10 @@ int btrfs_ordered_update_i_size(struct inode *inode, ...@@ -655,10 +702,10 @@ int btrfs_ordered_update_i_size(struct inode *inode,
* between our ordered extent and the next one. * between our ordered extent and the next one.
*/ */
test = rb_entry(node, struct btrfs_ordered_extent, rb_node); test = rb_entry(node, struct btrfs_ordered_extent, rb_node);
if (test->file_offset > entry_end(ordered)) if (test->file_offset > offset)
i_size_test = test->file_offset; i_size_test = test->file_offset;
} else { } else {
i_size_test = i_size_read(inode); i_size_test = i_size;
} }
/* /*
...@@ -667,15 +714,25 @@ int btrfs_ordered_update_i_size(struct inode *inode, ...@@ -667,15 +714,25 @@ int btrfs_ordered_update_i_size(struct inode *inode,
* are no delalloc bytes in this area, it is safe to update * are no delalloc bytes in this area, it is safe to update
* disk_i_size to the end of the region. * disk_i_size to the end of the region.
*/ */
if (i_size_test > entry_end(ordered) && if (i_size_test > offset &&
!test_range_bit(io_tree, entry_end(ordered), i_size_test - 1, !test_range_bit(io_tree, offset, i_size_test - 1,
EXTENT_DELALLOC, 0, NULL)) { EXTENT_DELALLOC, 0, NULL)) {
new_i_size = min_t(u64, i_size_test, i_size_read(inode)); new_i_size = min_t(u64, i_size_test, i_size);
} }
BTRFS_I(inode)->disk_i_size = new_i_size; BTRFS_I(inode)->disk_i_size = new_i_size;
ret = 0;
out: out:
/*
* we need to remove the ordered extent with the tree lock held
* so that other people calling this function don't find our fully
* processed ordered entry and skip updating the i_size
*/
if (ordered)
__btrfs_remove_ordered_extent(inode, ordered);
mutex_unlock(&tree->mutex); mutex_unlock(&tree->mutex);
return 0; if (ordered)
wake_up(&ordered->wait);
return ret;
} }
/* /*
......
...@@ -150,7 +150,7 @@ void btrfs_start_ordered_extent(struct inode *inode, ...@@ -150,7 +150,7 @@ void btrfs_start_ordered_extent(struct inode *inode,
int btrfs_wait_ordered_range(struct inode *inode, u64 start, u64 len); int btrfs_wait_ordered_range(struct inode *inode, u64 start, u64 len);
struct btrfs_ordered_extent * struct btrfs_ordered_extent *
btrfs_lookup_first_ordered_extent(struct inode * inode, u64 file_offset); btrfs_lookup_first_ordered_extent(struct inode * inode, u64 file_offset);
int btrfs_ordered_update_i_size(struct inode *inode, int btrfs_ordered_update_i_size(struct inode *inode, u64 offset,
struct btrfs_ordered_extent *ordered); struct btrfs_ordered_extent *ordered);
int btrfs_find_ordered_sum(struct inode *inode, u64 offset, u64 disk_bytenr, u32 *sum); int btrfs_find_ordered_sum(struct inode *inode, u64 offset, u64 disk_bytenr, u32 *sum);
int btrfs_wait_ordered_extents(struct btrfs_root *root, int nocow_only); int btrfs_wait_ordered_extents(struct btrfs_root *root, int nocow_only);
......
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