Skip to content

Commit 1b3b4a1

Browse files
Trond MyklebustTrond Myklebust
authored andcommitted
NFS: Fix a write request leak in nfs_invalidate_page()
Ryusuke Konishi says: The recent truncate_complete_page() clears the dirty flag from a page before calling a_ops->invalidatepage(), ^^^^^^ static void truncate_complete_page(struct address_space *mapping, struct page *page) { ... cancel_dirty_page(page, PAGE_CACHE_SIZE); <--- Inserted here at kernel 2.6.20 if (PagePrivate(page)) do_invalidatepage(page, 0); ---> will call a_ops->invalidatepage() ... } and this is disturbing nfs_wb_page_priority() from calling nfs_writepage_locked() that is expected to handle the pending request (=nfs_page) associated with the page. int nfs_wb_page_priority(struct inode *inode, struct page *page, int how) { ... if (clear_page_dirty_for_io(page)) { ret = nfs_writepage_locked(page, &wbc); if (ret < 0) goto out; } ... } Since truncate_complete_page() will get rid of the page after a_ops->invalidatepage() returns, the request (=nfs_page) associated with the page becomes a garbage in nfs_inode->nfs_page_tree. ------------------------ Fix this by ensuring that nfs_wb_page_priority() recognises that it may also need to clear out non-dirty pages that have an nfs_page associated with them. Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
1 parent 7d1cca7 commit 1b3b4a1

File tree

3 files changed

+46
-1
lines changed

3 files changed

+46
-1
lines changed

fs/nfs/file.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ static void nfs_invalidate_page(struct page *page, unsigned long offset)
316316
if (offset != 0)
317317
return;
318318
/* Cancel any unstarted writes on this page */
319-
nfs_wb_page_priority(page->mapping->host, page, FLUSH_INVALIDATE);
319+
nfs_wb_page_cancel(page->mapping->host, page);
320320
}
321321

322322
static int nfs_release_page(struct page *page, gfp_t gfp)

fs/nfs/write.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,50 @@ int nfs_sync_mapping_range(struct address_space *mapping, loff_t range_start, lo
13961396
return ret;
13971397
}
13981398

1399+
int nfs_wb_page_cancel(struct inode *inode, struct page *page)
1400+
{
1401+
struct nfs_page *req;
1402+
loff_t range_start = page_offset(page);
1403+
loff_t range_end = range_start + (loff_t)(PAGE_CACHE_SIZE - 1);
1404+
struct writeback_control wbc = {
1405+
.bdi = page->mapping->backing_dev_info,
1406+
.sync_mode = WB_SYNC_ALL,
1407+
.nr_to_write = LONG_MAX,
1408+
.range_start = range_start,
1409+
.range_end = range_end,
1410+
};
1411+
int ret = 0;
1412+
1413+
BUG_ON(!PageLocked(page));
1414+
for (;;) {
1415+
req = nfs_page_find_request(page);
1416+
if (req == NULL)
1417+
goto out;
1418+
if (test_bit(PG_NEED_COMMIT, &req->wb_flags)) {
1419+
nfs_release_request(req);
1420+
break;
1421+
}
1422+
if (nfs_lock_request_dontget(req)) {
1423+
nfs_inode_remove_request(req);
1424+
/*
1425+
* In case nfs_inode_remove_request has marked the
1426+
* page as being dirty
1427+
*/
1428+
cancel_dirty_page(page, PAGE_CACHE_SIZE);
1429+
nfs_unlock_request(req);
1430+
break;
1431+
}
1432+
ret = nfs_wait_on_request(req);
1433+
if (ret < 0)
1434+
goto out;
1435+
}
1436+
if (!PagePrivate(page))
1437+
return 0;
1438+
ret = nfs_sync_mapping_wait(page->mapping, &wbc, FLUSH_INVALIDATE);
1439+
out:
1440+
return ret;
1441+
}
1442+
13991443
int nfs_wb_page_priority(struct inode *inode, struct page *page, int how)
14001444
{
14011445
loff_t range_start = page_offset(page);

include/linux/nfs_fs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ extern int nfs_sync_mapping_range(struct address_space *, loff_t, loff_t, int);
431431
extern int nfs_wb_all(struct inode *inode);
432432
extern int nfs_wb_page(struct inode *inode, struct page* page);
433433
extern int nfs_wb_page_priority(struct inode *inode, struct page* page, int how);
434+
extern int nfs_wb_page_cancel(struct inode *inode, struct page* page);
434435
#if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
435436
extern int nfs_commit_inode(struct inode *, int);
436437
extern struct nfs_write_data *nfs_commit_alloc(void);

0 commit comments

Comments
 (0)