afs: Fix afs_invalidatepage to adjust the dirty region

Fix afs_invalidatepage() to adjust the dirty region recorded in
page->private when truncating a page.  If the dirty region is entirely
removed, then the private data is cleared and the page dirty state is
cleared.

Without this, if the page is truncated and then expanded again by truncate,
zeros from the expanded, but no-longer dirty region may get written back to
the server if the page gets laundered due to a conflicting 3rd-party write.

It mustn't, however, shorten the dirty region of the page if that page is
still mmapped and has been marked dirty by afs_page_mkwrite(), so a flag is
stored in page->private to record this.

Fixes: 4343d00872 ("afs: Get rid of the afs_writeback record")
Signed-off-by: David Howells <dhowells@redhat.com>
This commit is contained in:
David Howells 2020-10-22 14:08:23 +01:00
parent 65dd2d6072
commit f86726a69d
4 changed files with 79 additions and 14 deletions

View File

@ -601,6 +601,63 @@ static int afs_readpages(struct file *file, struct address_space *mapping,
return ret;
}
/*
* Adjust the dirty region of the page on truncation or full invalidation,
* getting rid of the markers altogether if the region is entirely invalidated.
*/
static void afs_invalidate_dirty(struct page *page, unsigned int offset,
unsigned int length)
{
struct afs_vnode *vnode = AFS_FS_I(page->mapping->host);
unsigned long priv;
unsigned int f, t, end = offset + length;
priv = page_private(page);
/* we clean up only if the entire page is being invalidated */
if (offset == 0 && length == thp_size(page))
goto full_invalidate;
/* If the page was dirtied by page_mkwrite(), the PTE stays writable
* and we don't get another notification to tell us to expand it
* again.
*/
if (afs_is_page_dirty_mmapped(priv))
return;
/* We may need to shorten the dirty region */
f = afs_page_dirty_from(priv);
t = afs_page_dirty_to(priv);
if (t <= offset || f >= end)
return; /* Doesn't overlap */
if (f < offset && t > end)
return; /* Splits the dirty region - just absorb it */
if (f >= offset && t <= end)
goto undirty;
if (f < offset)
t = offset;
else
f = end;
if (f == t)
goto undirty;
priv = afs_page_dirty(f, t);
set_page_private(page, priv);
trace_afs_page_dirty(vnode, tracepoint_string("trunc"), page->index, priv);
return;
undirty:
trace_afs_page_dirty(vnode, tracepoint_string("undirty"), page->index, priv);
clear_page_dirty_for_io(page);
full_invalidate:
priv = (unsigned long)detach_page_private(page);
trace_afs_page_dirty(vnode, tracepoint_string("inval"), page->index, priv);
}
/*
* invalidate part or all of a page
* - release a page and clean up its private data if offset is 0 (indicating
@ -609,29 +666,23 @@ static int afs_readpages(struct file *file, struct address_space *mapping,
static void afs_invalidatepage(struct page *page, unsigned int offset,
unsigned int length)
{
struct afs_vnode *vnode = AFS_FS_I(page->mapping->host);
unsigned long priv;
_enter("{%lu},%u,%u", page->index, offset, length);
BUG_ON(!PageLocked(page));
#ifdef CONFIG_AFS_FSCACHE
/* we clean up only if the entire page is being invalidated */
if (offset == 0 && length == PAGE_SIZE) {
#ifdef CONFIG_AFS_FSCACHE
if (PageFsCache(page)) {
struct afs_vnode *vnode = AFS_FS_I(page->mapping->host);
fscache_wait_on_page_write(vnode->cache, page);
fscache_uncache_page(vnode->cache, page);
}
}
#endif
if (PagePrivate(page)) {
priv = (unsigned long)detach_page_private(page);
trace_afs_page_dirty(vnode, tracepoint_string("inval"),
page->index, priv);
}
}
if (PagePrivate(page))
afs_invalidate_dirty(page, offset, length);
_leave("");
}

View File

@ -864,11 +864,13 @@ struct afs_vnode_cache_aux {
* 0...PAGE_SIZE inclusive, so we can't support 64K pages on a 32-bit system.
*/
#ifdef CONFIG_64BIT
#define __AFS_PAGE_PRIV_MASK 0xffffffffUL
#define __AFS_PAGE_PRIV_MASK 0x7fffffffUL
#define __AFS_PAGE_PRIV_SHIFT 32
#define __AFS_PAGE_PRIV_MMAPPED 0x80000000UL
#else
#define __AFS_PAGE_PRIV_MASK 0xffffUL
#define __AFS_PAGE_PRIV_MASK 0x7fffUL
#define __AFS_PAGE_PRIV_SHIFT 16
#define __AFS_PAGE_PRIV_MMAPPED 0x8000UL
#endif
static inline size_t afs_page_dirty_from(unsigned long priv)
@ -886,6 +888,16 @@ static inline unsigned long afs_page_dirty(size_t from, size_t to)
return ((unsigned long)(to - 1) << __AFS_PAGE_PRIV_SHIFT) | from;
}
static inline unsigned long afs_page_dirty_mmapped(unsigned long priv)
{
return priv | __AFS_PAGE_PRIV_MMAPPED;
}
static inline bool afs_is_page_dirty_mmapped(unsigned long priv)
{
return priv & __AFS_PAGE_PRIV_MMAPPED;
}
#include <trace/events/afs.h>
/*****************************************************************************/

View File

@ -867,6 +867,7 @@ vm_fault_t afs_page_mkwrite(struct vm_fault *vmf)
wait_on_page_writeback(vmf->page);
priv = afs_page_dirty(0, PAGE_SIZE);
priv = afs_page_dirty_mmapped(priv);
trace_afs_page_dirty(vnode, tracepoint_string("mkwrite"),
vmf->page->index, priv);
if (PagePrivate(vmf->page))

View File

@ -986,10 +986,11 @@ TRACE_EVENT(afs_page_dirty,
__entry->priv = priv;
),
TP_printk("vn=%p %lx %s %zx-%zx",
TP_printk("vn=%p %lx %s %zx-%zx%s",
__entry->vnode, __entry->page, __entry->where,
afs_page_dirty_from(__entry->priv),
afs_page_dirty_to(__entry->priv))
afs_page_dirty_to(__entry->priv),
afs_is_page_dirty_mmapped(__entry->priv) ? " M" : "")
);
TRACE_EVENT(afs_call_state,