Commit b38ef71c authored by Filipe Manana's avatar Filipe Manana Committed by Chris Mason

Btrfs: ensure ordered extent errors aren't missed on fsync

When doing a fsync with a fast path we have a time window where we can miss
the fact that writeback of some file data failed, and therefore we endup
returning success (0) from fsync when we should return an error.
The steps that lead to this are the following:

1) We start all ordered extents by calling filemap_fdatawrite_range();

2) We do some other work like locking the inode's i_mutex, start a transaction,
   start a log transaction, etc;

3) We enter btrfs_log_inode(), acquire the inode's log_mutex and collect all the
   ordered extents from inode's ordered tree into a list;

4) But by the time we do ordered extent collection, some ordered extents we started
   at step 1) might have already completed with an error, and therefore we didn't
   found them in the ordered tree and had no idea they finished with an error. This
   makes our fsync return success (0) to userspace, but has no bad effects on the log
   like for example insertion of file extent items into the log that point to unwritten
   extents, because the invalid extent maps were removed before the ordered extent
   completed (in inode.c:btrfs_finish_ordered_io).

So after collecting the ordered extents just check if the inode's i_mapping has any
error flags set (AS_EIO or AS_ENOSPC) and leave with an error if it does. Whenever
writeback fails for a page of an ordered extent, we call mapping_set_error (done in
extent_io.c:end_extent_writepage, called by extent_io.c:end_bio_extent_writepage)
that sets one of those error flags in the inode's i_mapping flags.

This change also has the side effect of fixing the issue where for fast fsyncs we
never checked/cleared the error flags from the inode's i_mapping flags, which means
that a full fsync performed after a fast fsync could get such errors that belonged
to the fast fsync - because the full fsync calls btrfs_wait_ordered_range() which
calls filemap_fdatawait_range(), and the later checks for and clears those flags,
while for fast fsyncs we never call filemap_fdatawait_range() or anything else
that checks for and clears the error flags from the inode's i_mapping.
Signed-off-by: default avatarFilipe Manana <fdmanana@suse.com>
Signed-off-by: default avatarChris Mason <clm@fb.com>
parent 0870295b
...@@ -3866,6 +3866,7 @@ int btrfs_prealloc_file_range_trans(struct inode *inode, ...@@ -3866,6 +3866,7 @@ int btrfs_prealloc_file_range_trans(struct inode *inode,
struct btrfs_trans_handle *trans, int mode, struct btrfs_trans_handle *trans, int mode,
u64 start, u64 num_bytes, u64 min_size, u64 start, u64 num_bytes, u64 min_size,
loff_t actual_len, u64 *alloc_hint); loff_t actual_len, u64 *alloc_hint);
int btrfs_inode_check_errors(struct inode *inode);
extern const struct dentry_operations btrfs_dentry_operations; extern const struct dentry_operations btrfs_dentry_operations;
/* ioctl.c */ /* ioctl.c */
......
...@@ -9464,6 +9464,21 @@ static int btrfs_tmpfile(struct inode *dir, struct dentry *dentry, umode_t mode) ...@@ -9464,6 +9464,21 @@ static int btrfs_tmpfile(struct inode *dir, struct dentry *dentry, umode_t mode)
} }
/* Inspired by filemap_check_errors() */
int btrfs_inode_check_errors(struct inode *inode)
{
int ret = 0;
if (test_bit(AS_ENOSPC, &inode->i_mapping->flags) &&
test_and_clear_bit(AS_ENOSPC, &inode->i_mapping->flags))
ret = -ENOSPC;
if (test_bit(AS_EIO, &inode->i_mapping->flags) &&
test_and_clear_bit(AS_EIO, &inode->i_mapping->flags))
ret = -EIO;
return ret;
}
static const struct inode_operations btrfs_dir_inode_operations = { static const struct inode_operations btrfs_dir_inode_operations = {
.getattr = btrfs_getattr, .getattr = btrfs_getattr,
.lookup = btrfs_lookup, .lookup = btrfs_lookup,
......
...@@ -3635,6 +3635,12 @@ static int wait_ordered_extents(struct btrfs_trans_handle *trans, ...@@ -3635,6 +3635,12 @@ static int wait_ordered_extents(struct btrfs_trans_handle *trans,
test_bit(BTRFS_ORDERED_IOERR, &ordered->flags))); test_bit(BTRFS_ORDERED_IOERR, &ordered->flags)));
if (test_bit(BTRFS_ORDERED_IOERR, &ordered->flags)) { if (test_bit(BTRFS_ORDERED_IOERR, &ordered->flags)) {
/*
* Clear the AS_EIO/AS_ENOSPC flags from the inode's
* i_mapping flags, so that the next fsync won't get
* an outdated io error too.
*/
btrfs_inode_check_errors(inode);
*ordered_io_error = true; *ordered_io_error = true;
break; break;
} }
...@@ -4098,6 +4104,21 @@ static int btrfs_log_inode(struct btrfs_trans_handle *trans, ...@@ -4098,6 +4104,21 @@ static int btrfs_log_inode(struct btrfs_trans_handle *trans,
btrfs_release_path(path); btrfs_release_path(path);
btrfs_release_path(dst_path); btrfs_release_path(dst_path);
if (fast_search) { if (fast_search) {
/*
* Some ordered extents started by fsync might have completed
* before we collected the ordered extents in logged_list, which
* means they're gone, not in our logged_list nor in the inode's
* ordered tree. We want the application/user space to know an
* error happened while attempting to persist file data so that
* it can take proper action. If such error happened, we leave
* without writing to the log tree and the fsync must report the
* file data write error and not commit the current transaction.
*/
err = btrfs_inode_check_errors(inode);
if (err) {
ctx->io_err = err;
goto out_unlock;
}
ret = btrfs_log_changed_extents(trans, root, inode, dst_path, ret = btrfs_log_changed_extents(trans, root, inode, dst_path,
&logged_list, ctx); &logged_list, ctx);
if (ret) { if (ret) {
......
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