From e0ace6ca98bef0d8d354040f13ffc0a498813ee9 Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 22 Nov 2023 17:18:17 +0000 Subject: netfs, cachefiles: Pass upper bound length to allow expansion Make netfslib pass the maximum length to the ->prepare_write() op to tell the cache how much it can expand the length of a write to. This allows a write to the server at the end of a file to be limited to a few bytes whilst writing an entire block to the cache (something required by direct I/O). Signed-off-by: David Howells Reviewed-by: Jeff Layton cc: linux-cachefs@redhat.com cc: linux-fsdevel@vger.kernel.org cc: linux-mm@kvack.org --- fs/cachefiles/io.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'fs/cachefiles/io.c') diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index 009d23cd435b..bffffedce4a9 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -518,7 +518,7 @@ cachefiles_prepare_ondemand_read(struct netfs_cache_resources *cres, */ int __cachefiles_prepare_write(struct cachefiles_object *object, struct file *file, - loff_t *_start, size_t *_len, + loff_t *_start, size_t *_len, size_t upper_len, bool no_space_allocated_yet) { struct cachefiles_cache *cache = object->volume->cache; @@ -530,6 +530,8 @@ int __cachefiles_prepare_write(struct cachefiles_object *object, down = start - round_down(start, PAGE_SIZE); *_start = start - down; *_len = round_up(down + len, PAGE_SIZE); + if (down < start || *_len > upper_len) + return -ENOBUFS; /* We need to work out whether there's sufficient disk space to perform * the write - but we can skip that check if we have space already @@ -592,8 +594,8 @@ check_space: } static int cachefiles_prepare_write(struct netfs_cache_resources *cres, - loff_t *_start, size_t *_len, loff_t i_size, - bool no_space_allocated_yet) + loff_t *_start, size_t *_len, size_t upper_len, + loff_t i_size, bool no_space_allocated_yet) { struct cachefiles_object *object = cachefiles_cres_object(cres); struct cachefiles_cache *cache = object->volume->cache; @@ -609,7 +611,7 @@ static int cachefiles_prepare_write(struct netfs_cache_resources *cres, cachefiles_begin_secure(cache, &saved_cred); ret = __cachefiles_prepare_write(object, cachefiles_cres_file(cres), - _start, _len, + _start, _len, upper_len, no_space_allocated_yet); cachefiles_end_secure(cache, saved_cred); return ret; -- cgit From 7097c96411d22a1b3f6370dfd7eb2e3b7b83ff98 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 2 Jan 2024 16:28:04 +0000 Subject: cachefiles: Fix __cachefiles_prepare_write() Fix __cachefiles_prepare_write() to correctly determine whether the requested write will fit correctly with the DIO alignment. Reported-by: Gao Xiang Signed-off-by: David Howells Tested-by: Yiqun Leng Tested-by: Jia Zhu cc: Jeff Layton cc: linux-cachefs@redhat.com cc: linux-erofs@lists.ozlabs.org cc: linux-fsdevel@vger.kernel.org cc: linux-mm@kvack.org --- fs/cachefiles/io.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'fs/cachefiles/io.c') diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index bffffedce4a9..7529b40bc95a 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -522,16 +522,22 @@ int __cachefiles_prepare_write(struct cachefiles_object *object, bool no_space_allocated_yet) { struct cachefiles_cache *cache = object->volume->cache; - loff_t start = *_start, pos; - size_t len = *_len, down; + unsigned long long start = *_start, pos; + size_t len = *_len; int ret; /* Round to DIO size */ - down = start - round_down(start, PAGE_SIZE); - *_start = start - down; - *_len = round_up(down + len, PAGE_SIZE); - if (down < start || *_len > upper_len) + start = round_down(*_start, PAGE_SIZE); + if (start != *_start) { + kleave(" = -ENOBUFS [down]"); + return -ENOBUFS; + } + if (*_len > upper_len) { + kleave(" = -ENOBUFS [up]"); return -ENOBUFS; + } + + *_len = round_up(len, PAGE_SIZE); /* We need to work out whether there's sufficient disk space to perform * the write - but we can skip that check if we have space already @@ -542,7 +548,7 @@ int __cachefiles_prepare_write(struct cachefiles_object *object, pos = cachefiles_inject_read_error(); if (pos == 0) - pos = vfs_llseek(file, *_start, SEEK_DATA); + pos = vfs_llseek(file, start, SEEK_DATA); if (pos < 0 && pos >= (loff_t)-MAX_ERRNO) { if (pos == -ENXIO) goto check_space; /* Unallocated tail */ @@ -550,7 +556,7 @@ int __cachefiles_prepare_write(struct cachefiles_object *object, cachefiles_trace_seek_error); return pos; } - if ((u64)pos >= (u64)*_start + *_len) + if (pos >= start + *_len) goto check_space; /* Unallocated region */ /* We have a block that's at least partially filled - if we're low on @@ -563,13 +569,13 @@ int __cachefiles_prepare_write(struct cachefiles_object *object, pos = cachefiles_inject_read_error(); if (pos == 0) - pos = vfs_llseek(file, *_start, SEEK_HOLE); + pos = vfs_llseek(file, start, SEEK_HOLE); if (pos < 0 && pos >= (loff_t)-MAX_ERRNO) { trace_cachefiles_io_error(object, file_inode(file), pos, cachefiles_trace_seek_error); return pos; } - if ((u64)pos >= (u64)*_start + *_len) + if (pos >= start + *_len) return 0; /* Fully allocated */ /* Partially allocated, but insufficient space: cull. */ @@ -577,7 +583,7 @@ int __cachefiles_prepare_write(struct cachefiles_object *object, ret = cachefiles_inject_remove_error(); if (ret == 0) ret = vfs_fallocate(file, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, - *_start, *_len); + start, *_len); if (ret < 0) { trace_cachefiles_io_error(object, file_inode(file), ret, cachefiles_trace_fallocate_error); -- cgit From 92a714d727ec9e7ccfcc7432d348aba730145914 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 4 Jan 2024 15:52:11 +0000 Subject: netfs: Fix interaction between write-streaming and cachefiles culling An issue can occur between write-streaming (storing dirty data in partial non-uptodate pages) and a cachefiles object being culled to make space. The problem occurs because the cache object is only marked in use while there are files open using it. Once it has been released, it can be culled and the cookie marked disabled. At this point, a streaming write is permitted to occur (if the cache is active, we require pages to be prefetched and cached), but the cache can become active again before this gets flushed out - and then two effects can occur: (1) The cache may be asked to write out a region that's less than its DIO block size (assumed by cachefiles to be PAGE_SIZE) - and this causes one of two debugging statements to be emitted. (2) netfs_how_to_modify() gets confused because it sees a page that isn't allowed to be non-uptodate being uptodate and tries to prefetch it - leading to a warning that PG_fscache is set twice. Fix this by the following means: (1) Add a netfs_inode flag to disallow write-streaming to an inode and set it if we ever do local caching of that inode. It remains set for the lifetime of that inode - even if the cookie becomes disabled. (2) If the no-write-streaming flag is set, then make netfs_how_to_modify() always want to prefetch instead. (3) If netfs_how_to_modify() decides it wants to prefetch a folio, but that folio has write-streamed data in it, then it requires the folio be flushed first. (4) Export a counter of the number of times we wanted to prefetch a non-uptodate page, but found it had write-streamed data in it. (5) Export a counter of the number of times we cancelled a write to the cache because it didn't DIO align and remove the debug statements. Reported-by: Marc Dionne Signed-off-by: David Howells cc: Jeff Layton cc: linux-cachefs@redhat.com cc: linux-erofs@lists.ozlabs.org cc: linux-fsdevel@vger.kernel.org cc: linux-mm@kvack.org --- fs/cachefiles/io.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'fs/cachefiles/io.c') diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index 7529b40bc95a..3eec26967437 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -528,12 +528,12 @@ int __cachefiles_prepare_write(struct cachefiles_object *object, /* Round to DIO size */ start = round_down(*_start, PAGE_SIZE); - if (start != *_start) { - kleave(" = -ENOBUFS [down]"); - return -ENOBUFS; - } - if (*_len > upper_len) { - kleave(" = -ENOBUFS [up]"); + if (start != *_start || *_len > upper_len) { + /* Probably asked to cache a streaming write written into the + * pagecache when the cookie was temporarily out of service to + * culling. + */ + fscache_count_dio_misfit(); return -ENOBUFS; } -- cgit From 3d1d4aa0cc13b1883a5a56c945837a2e0ecb5143 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 8 Jan 2024 10:02:55 +0000 Subject: cachefiles: Fix signed/unsigned mixup In __cachefiles_prepare_write(), the start and pos variables were made unsigned 64-bit so that the casts in the checking could be got rid of - which should be fine since absolute file offsets can't be negative, except that an error code may be obtained from vfs_llseek(), which *would* be negative. This breaks the error check. Fix this for now by reverting pos and start to be signed and putting back the casts. Unfortunately, the error value checks cannot be replaced with IS_ERR_VALUE() as long might be 32-bits. Fixes: 7097c96411d2 ("cachefiles: Fix __cachefiles_prepare_write()") Reported-by: Simon Horman Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202401071152.DbKqMQMu-lkp@intel.com/ Signed-off-by: David Howells Reviewed-by: Simon Horman Reviewed-by: Gao Xiang cc: Yiqun Leng cc: Jia Zhu cc: Jeff Layton cc: linux-cachefs@redhat.com cc: linux-erofs@lists.ozlabs.org cc: linux-fsdevel@vger.kernel.org cc: linux-mm@kvack.org --- fs/cachefiles/io.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'fs/cachefiles/io.c') diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index 3eec26967437..9a2cb2868e90 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -522,7 +522,7 @@ int __cachefiles_prepare_write(struct cachefiles_object *object, bool no_space_allocated_yet) { struct cachefiles_cache *cache = object->volume->cache; - unsigned long long start = *_start, pos; + loff_t start = *_start, pos; size_t len = *_len; int ret; @@ -556,7 +556,7 @@ int __cachefiles_prepare_write(struct cachefiles_object *object, cachefiles_trace_seek_error); return pos; } - if (pos >= start + *_len) + if ((u64)pos >= (u64)start + *_len) goto check_space; /* Unallocated region */ /* We have a block that's at least partially filled - if we're low on @@ -575,7 +575,7 @@ int __cachefiles_prepare_write(struct cachefiles_object *object, cachefiles_trace_seek_error); return pos; } - if (pos >= start + *_len) + if ((u64)pos >= (u64)start + *_len) return 0; /* Fully allocated */ /* Partially allocated, but insufficient space: cull. */ -- cgit