Fix afs to take a ref on a page when it sets PG_private on it and to drop
the ref when removing the flag.

Note that in afs_write_begin(), a lot of the time, PG_private is already
set on a page to which we're going to add some data.  In such a case, we
leave the bit set and mustn't increment the page count.  To this end, make
TestSetPagePrivate() available.

Fixes: 31143d5d515e ("AFS: implement basic file write support")
Reported-by: Matthew Wilcox (Oracle) <wi...@infradead.org>
Signed-off-by: David Howells <dhowe...@redhat.com>
---

 fs/afs/dir.c               |    3 +++
 fs/afs/dir_edit.c          |    1 +
 fs/afs/file.c              |    2 ++
 fs/afs/write.c             |    9 +++++++--
 include/linux/page-flags.h |    1 +
 5 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/fs/afs/dir.c b/fs/afs/dir.c
index 1d2e61e0ab04..064eb66c33e9 100644
--- a/fs/afs/dir.c
+++ b/fs/afs/dir.c
@@ -283,6 +283,7 @@ static struct afs_read *afs_read_dir(struct afs_vnode 
*dvnode, struct key *key)
 
                        set_page_private(req->pages[i], 1);
                        SetPagePrivate(req->pages[i]);
+                       get_page(req->pages[i]);
                        unlock_page(req->pages[i]);
                        i++;
                } else {
@@ -1977,6 +1978,7 @@ static int afs_dir_releasepage(struct page *page, gfp_t 
gfp_flags)
 
        set_page_private(page, 0);
        ClearPagePrivate(page);
+       put_page(page);
 
        /* The directory will need reloading. */
        if (test_and_clear_bit(AFS_VNODE_DIR_VALID, &dvnode->flags))
@@ -2006,5 +2008,6 @@ static void afs_dir_invalidatepage(struct page *page, 
unsigned int offset,
        if (offset == 0 && length == PAGE_SIZE) {
                set_page_private(page, 0);
                ClearPagePrivate(page);
+               put_page(page);
        }
 }
diff --git a/fs/afs/dir_edit.c b/fs/afs/dir_edit.c
index b108528bf010..997f6798beee 100644
--- a/fs/afs/dir_edit.c
+++ b/fs/afs/dir_edit.c
@@ -246,6 +246,7 @@ void afs_edit_dir_add(struct afs_vnode *vnode,
                        if (!PagePrivate(page)) {
                                set_page_private(page, 1);
                                SetPagePrivate(page);
+                               get_page(page);
                        }
                        dir_page = kmap(page);
                }
diff --git a/fs/afs/file.c b/fs/afs/file.c
index 91225421ad37..7dafa2266048 100644
--- a/fs/afs/file.c
+++ b/fs/afs/file.c
@@ -632,6 +632,7 @@ static void afs_invalidatepage(struct page *page, unsigned 
int offset,
                                             page->index, priv);
                        set_page_private(page, 0);
                        ClearPagePrivate(page);
+                       put_page(page);
                }
        }
 
@@ -666,6 +667,7 @@ static int afs_releasepage(struct page *page, gfp_t 
gfp_flags)
                                     page->index, priv);
                set_page_private(page, 0);
                ClearPagePrivate(page);
+               put_page(page);
        }
 
        /* indicate that the page can be released */
diff --git a/fs/afs/write.c b/fs/afs/write.c
index b937ec047ec9..29685947324e 100644
--- a/fs/afs/write.c
+++ b/fs/afs/write.c
@@ -151,7 +151,8 @@ int afs_write_begin(struct file *file, struct address_space 
*mapping,
        priv |= f;
        trace_afs_page_dirty(vnode, tracepoint_string("begin"),
                             page->index, priv);
-       SetPagePrivate(page);
+       if (!TestSetPagePrivate(page))
+               get_page(page);
        set_page_private(page, priv);
        _leave(" = 0");
        return 0;
@@ -338,6 +339,8 @@ static void afs_pages_written_back(struct afs_vnode *vnode,
                        trace_afs_page_dirty(vnode, tracepoint_string("clear"),
                                             pv.pages[loop]->index, priv);
                        set_page_private(pv.pages[loop], 0);
+                       ClearPagePrivate(pv.pages[loop]);
+                       put_page(pv.pages[loop]);
                        end_page_writeback(pv.pages[loop]);
                }
                first += count;
@@ -863,7 +866,8 @@ vm_fault_t afs_page_mkwrite(struct vm_fault *vmf)
        priv |= 0; /* From */
        trace_afs_page_dirty(vnode, tracepoint_string("mkwrite"),
                             vmf->page->index, priv);
-       SetPagePrivate(vmf->page);
+       if (!TestSetPagePrivate(vmf->page))
+               get_page(vmf->page);
        set_page_private(vmf->page, priv);
        file_update_time(file);
 
@@ -930,6 +934,7 @@ int afs_launder_page(struct page *page)
                             page->index, priv);
        set_page_private(page, 0);
        ClearPagePrivate(page);
+       put_page(page);
 
 #ifdef CONFIG_AFS_FSCACHE
        if (PageFsCache(page)) {
diff --git a/include/linux/page-flags.h b/include/linux/page-flags.h
index 4f6ba9379112..37d65b55a6c6 100644
--- a/include/linux/page-flags.h
+++ b/include/linux/page-flags.h
@@ -365,6 +365,7 @@ PAGEFLAG(SwapBacked, swapbacked, PF_NO_TAIL)
  */
 PAGEFLAG(Private, private, PF_ANY) __SETPAGEFLAG(Private, private, PF_ANY)
        __CLEARPAGEFLAG(Private, private, PF_ANY)
+       TESTSETFLAG(Private, private, PF_ANY)
 PAGEFLAG(Private2, private_2, PF_ANY) TESTSCFLAG(Private2, private_2, PF_ANY)
 PAGEFLAG(OwnerPriv1, owner_priv_1, PF_ANY)
        TESTCLEARFLAG(OwnerPriv1, owner_priv_1, PF_ANY)


Reply via email to