summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJérôme Glisse <jglisse@redhat.com>2018-09-28 14:09:49 -0400
committerJérôme Glisse <jglisse@redhat.com>2018-09-28 14:09:49 -0400
commit01677bc039c791a16d5f82b3ef84917d62fac826 (patch)
tree805f6f7d8a047c59f6ee0ef91c95bd6386d12661
parenta5dbc0fe7e71d347067579f13579df372ec48389 (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.c16
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);