diff mbox series

[v5,33/40] netfs, cachefiles: Pass upper bound length to allow expansion

Message ID 20231221132400.1601991-34-dhowells@redhat.com (mailing list archive)
State New
Headers show
Series netfs, afs, 9p: Delegate high-level I/O to netfslib | expand

Commit Message

David Howells Dec. 21, 2023, 1:23 p.m. UTC
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 <dhowells@redhat.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
cc: linux-cachefs@redhat.com
cc: linux-fsdevel@vger.kernel.org
cc: linux-mm@kvack.org
---
 fs/cachefiles/internal.h |  2 +-
 fs/cachefiles/io.c       | 10 ++++++----
 fs/cachefiles/ondemand.c |  2 +-
 fs/netfs/fscache_io.c    |  2 +-
 fs/netfs/io.c            |  2 +-
 fs/netfs/objects.c       |  1 +
 fs/netfs/output.c        | 25 ++++++++++---------------
 fs/smb/client/fscache.c  |  2 +-
 include/linux/netfs.h    |  5 +++--
 9 files changed, 25 insertions(+), 26 deletions(-)

Comments

Gao Xiang Jan. 2, 2024, 2:04 p.m. UTC | #1
Hi David,

On 2023/12/21 21:23, David Howells wrote:
> 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 <dhowells@redhat.com>
> Reviewed-by: Jeff Layton <jlayton@kernel.org>
> cc: linux-cachefs@redhat.com
> cc: linux-fsdevel@vger.kernel.org
> cc: linux-mm@kvack.org
> ---
>   fs/cachefiles/internal.h |  2 +-
>   fs/cachefiles/io.c       | 10 ++++++----
>   fs/cachefiles/ondemand.c |  2 +-
>   fs/netfs/fscache_io.c    |  2 +-
>   fs/netfs/io.c            |  2 +-
>   fs/netfs/objects.c       |  1 +
>   fs/netfs/output.c        | 25 ++++++++++---------------
>   fs/smb/client/fscache.c  |  2 +-
>   include/linux/netfs.h    |  5 +++--
>   9 files changed, 25 insertions(+), 26 deletions(-)
> 
> diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h
> index 2ad58c465208..1af48d576a34 100644
> --- a/fs/cachefiles/internal.h
> +++ b/fs/cachefiles/internal.h
> @@ -233,7 +233,7 @@ extern bool cachefiles_begin_operation(struct netfs_cache_resources *cres,
>   				       enum fscache_want_state want_state);
>   extern 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);
>   extern int __cachefiles_write(struct cachefiles_object *object,
>   			      struct file *file,
> 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;

Sorry for bothering. We just found some strange when testing
today-next EROFS over fscache.

I'm not sure the meaning of
     if (down < start

For example, if start is page-aligned, down == 0.

so as long as start > 0 and page-aligned, it will return
-ENOBUFS.  Does it an intended behavior?

Thanks,
Gao Xiang
David Howells Jan. 2, 2024, 5:11 p.m. UTC | #2
Gao Xiang <hsiangkao@linux.alibaba.com> wrote:

> >   	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;
> 
> Sorry for bothering. We just found some strange when testing
> today-next EROFS over fscache.
> 
> I'm not sure the meaning of
>     if (down < start
> 
> For example, if start is page-aligned, down == 0.
> 
> so as long as start > 0 and page-aligned, it will return
> -ENOBUFS.  Does it an intended behavior?

Yeah, I think that's wrong.

Does the attached help?

David
---

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);
Gao Xiang Jan. 2, 2024, 8:37 p.m. UTC | #3
On 2024/1/3 01:11, David Howells wrote:
> Gao Xiang <hsiangkao@linux.alibaba.com> wrote:
> 
>>>    	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;
>>
>> Sorry for bothering. We just found some strange when testing
>> today-next EROFS over fscache.
>>
>> I'm not sure the meaning of
>>      if (down < start
>>
>> For example, if start is page-aligned, down == 0.
>>
>> so as long as start > 0 and page-aligned, it will return
>> -ENOBUFS.  Does it an intended behavior?
> 
> Yeah, I think that's wrong.
> 
> Does the attached help?

(+cc more people for testing)

Will test and feedback later.

Thanks,
Gao Xiang

> 
> David
> ---
> 
> 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);
>
Yiqun Leng Jan. 3, 2024, 9:18 a.m. UTC | #4
Tested-by: Yiqun Leng <yqleng@linux.alibaba.com>

Please get more test details in this link: 
https://tone.openanolis.cn/ws/nrh4nnio/test_result/106066

Thanks and Regards,
Yiqun Leng


On 2024/1/3 04:37, Gao Xiang wrote:
>
>
> On 2024/1/3 01:11, David Howells wrote:
>> Gao Xiang <hsiangkao@linux.alibaba.com> wrote:
>>
>>>>        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;
>>>
>>> Sorry for bothering. We just found some strange when testing
>>> today-next EROFS over fscache.
>>>
>>> I'm not sure the meaning of
>>>      if (down < start
>>>
>>> For example, if start is page-aligned, down == 0.
>>>
>>> so as long as start > 0 and page-aligned, it will return
>>> -ENOBUFS.  Does it an intended behavior?
>>
>> Yeah, I think that's wrong.
>>
>> Does the attached help?
>
> (+cc more people for testing)
>
> Will test and feedback later.
>
> Thanks,
> Gao Xiang
>
>>
>> David
>> ---
>>
>> 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);
>>
>
Jia Zhu Jan. 3, 2024, 10:15 a.m. UTC | #5
在 2024/1/3 01:11, David Howells 写道:
> Gao Xiang <hsiangkao@linux.alibaba.com> wrote:
> 
>>>    	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;
>>
>> Sorry for bothering. We just found some strange when testing
>> today-next EROFS over fscache.
>>
>> I'm not sure the meaning of
>>      if (down < start
>>
>> For example, if start is page-aligned, down == 0.
>>
>> so as long as start > 0 and page-aligned, it will return
>> -ENOBUFS.  Does it an intended behavior?
> 
> Yeah, I think that's wrong.
> 
> Does the attached help?
> 
> David
> ---

Tested-by: Jia Zhu <zhujia.zj@bytedance.com>

> 
> 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);
>
diff mbox series

Patch

diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h
index 2ad58c465208..1af48d576a34 100644
--- a/fs/cachefiles/internal.h
+++ b/fs/cachefiles/internal.h
@@ -233,7 +233,7 @@  extern bool cachefiles_begin_operation(struct netfs_cache_resources *cres,
 				       enum fscache_want_state want_state);
 extern 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);
 extern int __cachefiles_write(struct cachefiles_object *object,
 			      struct file *file,
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 @@  int __cachefiles_prepare_write(struct cachefiles_object *object,
 }
 
 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;
diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c
index 0254ed39f68c..9301d1eb0504 100644
--- a/fs/cachefiles/ondemand.c
+++ b/fs/cachefiles/ondemand.c
@@ -52,7 +52,7 @@  static ssize_t cachefiles_ondemand_fd_write_iter(struct kiocb *kiocb,
 		return -ENOBUFS;
 
 	cachefiles_begin_secure(cache, &saved_cred);
-	ret = __cachefiles_prepare_write(object, file, &pos, &len, true);
+	ret = __cachefiles_prepare_write(object, file, &pos, &len, len, true);
 	cachefiles_end_secure(cache, saved_cred);
 	if (ret < 0)
 		return ret;
diff --git a/fs/netfs/fscache_io.c b/fs/netfs/fscache_io.c
index 79171a687930..ad572f7ee897 100644
--- a/fs/netfs/fscache_io.c
+++ b/fs/netfs/fscache_io.c
@@ -237,7 +237,7 @@  void __fscache_write_to_cache(struct fscache_cookie *cookie,
 				    fscache_access_io_write) < 0)
 		goto abandon_free;
 
-	ret = cres->ops->prepare_write(cres, &start, &len, i_size, false);
+	ret = cres->ops->prepare_write(cres, &start, &len, len, i_size, false);
 	if (ret < 0)
 		goto abandon_end;
 
diff --git a/fs/netfs/io.c b/fs/netfs/io.c
index 01c7ff27228e..14c18be5aca0 100644
--- a/fs/netfs/io.c
+++ b/fs/netfs/io.c
@@ -199,7 +199,7 @@  static void netfs_rreq_do_write_to_cache(struct netfs_io_request *rreq)
 		}
 
 		ret = cres->ops->prepare_write(cres, &subreq->start, &subreq->len,
-					       rreq->i_size, true);
+					       subreq->len, rreq->i_size, true);
 		if (ret < 0) {
 			trace_netfs_failure(rreq, subreq, ret, netfs_fail_prepare_write);
 			trace_netfs_sreq(subreq, netfs_sreq_trace_write_skip);
diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c
index 93f1d7431199..b4e3bd836e5d 100644
--- a/fs/netfs/objects.c
+++ b/fs/netfs/objects.c
@@ -33,6 +33,7 @@  struct netfs_io_request *netfs_alloc_request(struct address_space *mapping,
 
 	rreq->start	= start;
 	rreq->len	= len;
+	rreq->upper_len	= len;
 	rreq->origin	= origin;
 	rreq->netfs_ops	= ctx->ops;
 	rreq->mapping	= mapping;
diff --git a/fs/netfs/output.c b/fs/netfs/output.c
index 560cbcea0c0a..cc9065733b42 100644
--- a/fs/netfs/output.c
+++ b/fs/netfs/output.c
@@ -280,7 +280,7 @@  EXPORT_SYMBOL(netfs_queue_write_request);
  */
 static void netfs_set_up_write_to_cache(struct netfs_io_request *wreq)
 {
-	struct netfs_cache_resources *cres;
+	struct netfs_cache_resources *cres = &wreq->cache_resources;
 	struct netfs_io_subrequest *subreq;
 	struct netfs_inode *ctx = netfs_inode(wreq->inode);
 	struct fscache_cookie *cookie = netfs_i_cookie(ctx);
@@ -294,26 +294,21 @@  static void netfs_set_up_write_to_cache(struct netfs_io_request *wreq)
 	}
 
 	_debug("write to cache");
-	subreq = netfs_create_write_request(wreq, NETFS_WRITE_TO_CACHE, start, len,
-					    netfs_write_to_cache_op_worker);
-	if (!subreq)
+	ret = fscache_begin_write_operation(cres, cookie);
+	if (ret < 0)
 		return;
 
-	cres = &wreq->cache_resources;
-	ret = fscache_begin_read_operation(cres, cookie);
-	if (ret < 0) {
-		netfs_write_subrequest_terminated(subreq, ret, false);
+	ret = cres->ops->prepare_write(cres, &start, &len, wreq->upper_len,
+				       i_size_read(wreq->inode), true);
+	if (ret < 0)
 		return;
-	}
 
-	ret = cres->ops->prepare_write(cres, &start, &len, i_size_read(wreq->inode),
-				       true);
-	if (ret < 0) {
-		netfs_write_subrequest_terminated(subreq, ret, false);
+	subreq = netfs_create_write_request(wreq, NETFS_WRITE_TO_CACHE, start, len,
+					    netfs_write_to_cache_op_worker);
+	if (!subreq)
 		return;
-	}
 
-	netfs_queue_write_request(subreq);
+	netfs_write_to_cache_op(subreq);
 }
 
 /*
diff --git a/fs/smb/client/fscache.c b/fs/smb/client/fscache.c
index e5cad149f5a2..c4a3cb736881 100644
--- a/fs/smb/client/fscache.c
+++ b/fs/smb/client/fscache.c
@@ -180,7 +180,7 @@  static int fscache_fallback_write_pages(struct inode *inode, loff_t start, size_
 	if (ret < 0)
 		return ret;
 
-	ret = cres.ops->prepare_write(&cres, &start, &len, i_size_read(inode),
+	ret = cres.ops->prepare_write(&cres, &start, &len, len, i_size_read(inode),
 				      no_space_allocated_yet);
 	if (ret == 0)
 		ret = fscache_write(&cres, start, &iter, NULL, NULL);
diff --git a/include/linux/netfs.h b/include/linux/netfs.h
index a2e53ab06a1b..506c543f6888 100644
--- a/include/linux/netfs.h
+++ b/include/linux/netfs.h
@@ -262,6 +262,7 @@  struct netfs_io_request {
 	atomic_t		nr_copy_ops;	/* Number of copy-to-cache ops in progress */
 	size_t			submitted;	/* Amount submitted for I/O so far */
 	size_t			len;		/* Length of the request */
+	size_t			upper_len;	/* Length can be extended to here */
 	size_t			transferred;	/* Amount to be indicated as transferred */
 	short			error;		/* 0 or error that occurred */
 	enum netfs_io_origin	origin;		/* Origin of the request */
@@ -358,8 +359,8 @@  struct netfs_cache_ops {
 	 * actually do.
 	 */
 	int (*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);
 
 	/* Prepare an on-demand read operation, shortening it to a cached/uncached
 	 * boundary as appropriate.