diff options
author | Jérôme Glisse <jglisse@redhat.com> | 2018-09-28 14:09:49 -0400 |
---|---|---|
committer | Jérôme Glisse <jglisse@redhat.com> | 2018-09-28 14:09:49 -0400 |
commit | 01677bc039c791a16d5f82b3ef84917d62fac826 (patch) | |
tree | 805f6f7d8a047c59f6ee0ef91c95bd6386d12661 | |
parent | a5dbc0fe7e71d347067579f13579df372ec48389 (diff) |
fs/buffer: do not free buffers when a page is pinned by GUPgup
We do not want to free buffers of a page pinned by a GUP (get_user_
pages) as when the driver/code path that did the GUP release the page
it might try to set it dirty and filesystems which use buffers can
freak out if that happens (page being dirtyied without buffers).
Signed-off-by: Jérôme Glisse <jglisse@redhat.com>
-rw-r--r-- | fs/buffer.c | 16 |
1 files changed, 15 insertions, 1 deletions
diff --git a/fs/buffer.c b/fs/buffer.c index 6f1ae3ac9789..2f8fcf7dd5c1 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -3250,7 +3250,7 @@ int try_to_free_buffers(struct page *page) { struct address_space * const mapping = page->mapping; struct buffer_head *buffers_to_free = NULL; - int ret = 0; + int ret = 0, extra; BUG_ON(!PageLocked(page)); if (PageWriteback(page)) @@ -3261,6 +3261,20 @@ int try_to_free_buffers(struct page *page) goto out; } + /* Page has a mapping and buffer so 2 extra references. */ + extra = 2; + /* If page is isolated from lru then it has an extra refcount. */ + extra += PageLRU(page) ? 0 : 1; + /* + * We do not want to free buffers attach to a page that is pinned by a + * GUP (get_user_pages()) as the driver/code path that did GUP might do + * a set_page_dirty() on the page when releasing the page. To test for + * GUP we check the refcount, if it is bigger than mapcount it means + * that the page is pinned by some GUP and we should keep the buffers. + */ + if ((page_count(page) - extra) > page_mapcount(page)) + return 0; + spin_lock(&mapping->private_lock); ret = drop_buffers(page, &buffers_to_free); |