Commit ac3cc25f authored by Darrick J. Wong's avatar Darrick J. Wong Committed by Greg Kroah-Hartman

vfs: fix page locking deadlocks when deduping files

[ Upstream commit edc58dd0 ]

When dedupe wants to use the page cache to compare parts of two files
for dedupe, we must be very careful to handle locking correctly.  The
current code doesn't do this.  It must lock and unlock the page only
once if the two pages are the same, since the overlapping range check
doesn't catch this when blocksize < pagesize.  If the pages are distinct
but from the same file, we must observe page locking order and lock them
in order of increasing offset to avoid clashing with writeback locking.

Fixes: 876bec6f ("vfs: refactor clone/dedupe_file_range common functions")
Signed-off-by: default avatarDarrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: default avatarBill O'Donnell <billodo@redhat.com>
Reviewed-by: default avatarMatthew Wilcox (Oracle) <willy@infradead.org>
Signed-off-by: default avatarSasha Levin <sashal@kernel.org>
parent 9ea1fead
...@@ -1888,10 +1888,7 @@ int vfs_clone_file_range(struct file *file_in, loff_t pos_in, ...@@ -1888,10 +1888,7 @@ int vfs_clone_file_range(struct file *file_in, loff_t pos_in,
} }
EXPORT_SYMBOL(vfs_clone_file_range); EXPORT_SYMBOL(vfs_clone_file_range);
/* /* Read a page's worth of file data into the page cache. */
* Read a page's worth of file data into the page cache. Return the page
* locked.
*/
static struct page *vfs_dedupe_get_page(struct inode *inode, loff_t offset) static struct page *vfs_dedupe_get_page(struct inode *inode, loff_t offset)
{ {
struct address_space *mapping; struct address_space *mapping;
...@@ -1907,10 +1904,32 @@ static struct page *vfs_dedupe_get_page(struct inode *inode, loff_t offset) ...@@ -1907,10 +1904,32 @@ static struct page *vfs_dedupe_get_page(struct inode *inode, loff_t offset)
put_page(page); put_page(page);
return ERR_PTR(-EIO); return ERR_PTR(-EIO);
} }
lock_page(page);
return page; return page;
} }
/*
* Lock two pages, ensuring that we lock in offset order if the pages are from
* the same file.
*/
static void vfs_lock_two_pages(struct page *page1, struct page *page2)
{
/* Always lock in order of increasing index. */
if (page1->index > page2->index)
swap(page1, page2);
lock_page(page1);
if (page1 != page2)
lock_page(page2);
}
/* Unlock two pages, being careful not to unlock the same page twice. */
static void vfs_unlock_two_pages(struct page *page1, struct page *page2)
{
unlock_page(page1);
if (page1 != page2)
unlock_page(page2);
}
/* /*
* Compare extents of two files to see if they are the same. * Compare extents of two files to see if they are the same.
* Caller must have locked both inodes to prevent write races. * Caller must have locked both inodes to prevent write races.
...@@ -1948,10 +1967,24 @@ int vfs_dedupe_file_range_compare(struct inode *src, loff_t srcoff, ...@@ -1948,10 +1967,24 @@ int vfs_dedupe_file_range_compare(struct inode *src, loff_t srcoff,
dest_page = vfs_dedupe_get_page(dest, destoff); dest_page = vfs_dedupe_get_page(dest, destoff);
if (IS_ERR(dest_page)) { if (IS_ERR(dest_page)) {
error = PTR_ERR(dest_page); error = PTR_ERR(dest_page);
unlock_page(src_page);
put_page(src_page); put_page(src_page);
goto out_error; goto out_error;
} }
vfs_lock_two_pages(src_page, dest_page);
/*
* Now that we've locked both pages, make sure they're still
* mapped to the file data we're interested in. If not,
* someone is invalidating pages on us and we lose.
*/
if (!PageUptodate(src_page) || !PageUptodate(dest_page) ||
src_page->mapping != src->i_mapping ||
dest_page->mapping != dest->i_mapping) {
same = false;
goto unlock;
}
src_addr = kmap_atomic(src_page); src_addr = kmap_atomic(src_page);
dest_addr = kmap_atomic(dest_page); dest_addr = kmap_atomic(dest_page);
...@@ -1963,8 +1996,8 @@ int vfs_dedupe_file_range_compare(struct inode *src, loff_t srcoff, ...@@ -1963,8 +1996,8 @@ int vfs_dedupe_file_range_compare(struct inode *src, loff_t srcoff,
kunmap_atomic(dest_addr); kunmap_atomic(dest_addr);
kunmap_atomic(src_addr); kunmap_atomic(src_addr);
unlock_page(dest_page); unlock:
unlock_page(src_page); vfs_unlock_two_pages(src_page, dest_page);
put_page(dest_page); put_page(dest_page);
put_page(src_page); put_page(src_page);
......
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