Skip to content

Commit edc58dd

Browse files
committed
vfs: fix page locking deadlocks when deduping files
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: 876bec6 ("vfs: refactor clone/dedupe_file_range common functions") Signed-off-by: Darrick J. Wong <[email protected]> Reviewed-by: Bill O'Donnell <[email protected]> Reviewed-by: Matthew Wilcox (Oracle) <[email protected]>
1 parent 4529e6d commit edc58dd

File tree

1 file changed

+41
-8
lines changed

1 file changed

+41
-8
lines changed

fs/read_write.c

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1811,10 +1811,7 @@ static int generic_remap_check_len(struct inode *inode_in,
18111811
return (remap_flags & REMAP_FILE_DEDUP) ? -EBADE : -EINVAL;
18121812
}
18131813

1814-
/*
1815-
* Read a page's worth of file data into the page cache. Return the page
1816-
* locked.
1817-
*/
1814+
/* Read a page's worth of file data into the page cache. */
18181815
static struct page *vfs_dedupe_get_page(struct inode *inode, loff_t offset)
18191816
{
18201817
struct page *page;
@@ -1826,10 +1823,32 @@ static struct page *vfs_dedupe_get_page(struct inode *inode, loff_t offset)
18261823
put_page(page);
18271824
return ERR_PTR(-EIO);
18281825
}
1829-
lock_page(page);
18301826
return page;
18311827
}
18321828

1829+
/*
1830+
* Lock two pages, ensuring that we lock in offset order if the pages are from
1831+
* the same file.
1832+
*/
1833+
static void vfs_lock_two_pages(struct page *page1, struct page *page2)
1834+
{
1835+
/* Always lock in order of increasing index. */
1836+
if (page1->index > page2->index)
1837+
swap(page1, page2);
1838+
1839+
lock_page(page1);
1840+
if (page1 != page2)
1841+
lock_page(page2);
1842+
}
1843+
1844+
/* Unlock two pages, being careful not to unlock the same page twice. */
1845+
static void vfs_unlock_two_pages(struct page *page1, struct page *page2)
1846+
{
1847+
unlock_page(page1);
1848+
if (page1 != page2)
1849+
unlock_page(page2);
1850+
}
1851+
18331852
/*
18341853
* Compare extents of two files to see if they are the same.
18351854
* Caller must have locked both inodes to prevent write races.
@@ -1867,10 +1886,24 @@ static int vfs_dedupe_file_range_compare(struct inode *src, loff_t srcoff,
18671886
dest_page = vfs_dedupe_get_page(dest, destoff);
18681887
if (IS_ERR(dest_page)) {
18691888
error = PTR_ERR(dest_page);
1870-
unlock_page(src_page);
18711889
put_page(src_page);
18721890
goto out_error;
18731891
}
1892+
1893+
vfs_lock_two_pages(src_page, dest_page);
1894+
1895+
/*
1896+
* Now that we've locked both pages, make sure they're still
1897+
* mapped to the file data we're interested in. If not,
1898+
* someone is invalidating pages on us and we lose.
1899+
*/
1900+
if (!PageUptodate(src_page) || !PageUptodate(dest_page) ||
1901+
src_page->mapping != src->i_mapping ||
1902+
dest_page->mapping != dest->i_mapping) {
1903+
same = false;
1904+
goto unlock;
1905+
}
1906+
18741907
src_addr = kmap_atomic(src_page);
18751908
dest_addr = kmap_atomic(dest_page);
18761909

@@ -1882,8 +1915,8 @@ static int vfs_dedupe_file_range_compare(struct inode *src, loff_t srcoff,
18821915

18831916
kunmap_atomic(dest_addr);
18841917
kunmap_atomic(src_addr);
1885-
unlock_page(dest_page);
1886-
unlock_page(src_page);
1918+
unlock:
1919+
vfs_unlock_two_pages(src_page, dest_page);
18871920
put_page(dest_page);
18881921
put_page(src_page);
18891922

0 commit comments

Comments
 (0)