| From a30e577c96f59b1e1678ea5462432b09bf7d5cbc Mon Sep 17 00:00:00 2001 |
| From: Jeff Mahoney <jeffm@suse.com> |
| Date: Fri, 11 Sep 2015 21:44:17 -0400 |
| Subject: btrfs: skip waiting on ordered range for special files |
| |
| commit a30e577c96f59b1e1678ea5462432b09bf7d5cbc upstream. |
| |
| In btrfs_evict_inode, we properly truncate the page cache for evicted |
| inodes but then we call btrfs_wait_ordered_range for every inode as well. |
| It's the right thing to do for regular files but results in incorrect |
| behavior for device inodes for block devices. |
| |
| filemap_fdatawrite_range gets called with inode->i_mapping which gets |
| resolved to the block device inode before getting passed to |
| wbc_attach_fdatawrite_inode and ultimately to inode_to_bdi. What happens |
| next depends on whether there's an open file handle associated with the |
| inode. If there is, we write to the block device, which is unexpected |
| behavior. If there isn't, we through normally and inode->i_data is used. |
| We can also end up racing against open/close which can result in crashes |
| when i_mapping points to a block device inode that has been closed. |
| |
| Since there can't be any page cache associated with special file inodes, |
| it's safe to skip the btrfs_wait_ordered_range call entirely and avoid |
| the problem. |
| |
| Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=100911 |
| Tested-by: Christoph Biedl <linux-kernel.bfrz@manchmal.in-ulm.de> |
| Signed-off-by: Jeff Mahoney <jeffm@suse.com> |
| Reviewed-by: Filipe Manana <fdmanana@suse.com> |
| Signed-off-by: Zefan Li <lizefan@huawei.com> |
| --- |
| fs/btrfs/inode.c | 3 ++- |
| 1 file changed, 2 insertions(+), 1 deletion(-) |
| |
| --- a/fs/btrfs/inode.c |
| +++ b/fs/btrfs/inode.c |
| @@ -3685,7 +3685,8 @@ void btrfs_evict_inode(struct inode *ino |
| goto no_delete; |
| } |
| /* do we really want it for ->i_nlink > 0 and zero btrfs_root_refs? */ |
| - btrfs_wait_ordered_range(inode, 0, (u64)-1); |
| + if (!special_file(inode->i_mode)) |
| + btrfs_wait_ordered_range(inode, 0, (u64)-1); |
| |
| if (root->fs_info->log_root_recovering) { |
| BUG_ON(!list_empty(&BTRFS_I(inode)->i_orphan)); |