diff mbox series

[v2,1/3] fs: fork splice_file_range() from do_splice_direct()

Message ID 20231130141624.3338942-2-amir73il@gmail.com (mailing list archive)
State New, archived
Headers show
Series Avert possible deadlock with splice() and fanotify | expand

Commit Message

Amir Goldstein Nov. 30, 2023, 2:16 p.m. UTC
In preparation of calling do_splice_direct() without file_start_write()
held, create a new helper splice_file_range(), to be called from context
of ->copy_file_range() methods instead of do_splice_direct().

Currently, the only difference is that splice_file_range() does not take
flags argument and that it asserts that file_start_write() is held, but
we factor out a common helper do_splice_direct_actor() that will be used
later.

Use the new helper from __ceph_copy_file_range(), that was incorrectly
passing to do_splice_direct() the copy flags argument as splice flags.
The value of copy flags in ceph is always 0, so it is a smenatic bug fix.

Move the declaration of both helpers to linux/splice.h.

Reviewed-by: Jan Kara <jack@suse.cz>
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---
 fs/ceph/file.c         |  9 +++---
 fs/read_write.c        |  6 ++--
 fs/splice.c            | 71 ++++++++++++++++++++++++++++++------------
 include/linux/fs.h     |  2 --
 include/linux/splice.h | 13 +++++---
 5 files changed, 66 insertions(+), 35 deletions(-)

Comments

Jeff Layton Nov. 30, 2023, 4:27 p.m. UTC | #1
On Thu, 2023-11-30 at 16:16 +0200, Amir Goldstein wrote:
> In preparation of calling do_splice_direct() without file_start_write()
> held, create a new helper splice_file_range(), to be called from context
> of ->copy_file_range() methods instead of do_splice_direct().
> 
> Currently, the only difference is that splice_file_range() does not take
> flags argument and that it asserts that file_start_write() is held, but
> we factor out a common helper do_splice_direct_actor() that will be used
> later.
> 
> Use the new helper from __ceph_copy_file_range(), that was incorrectly
> passing to do_splice_direct() the copy flags argument as splice flags.
> The value of copy flags in ceph is always 0, so it is a smenatic bug fix.
> 
> Move the declaration of both helpers to linux/splice.h.
> 
> Reviewed-by: Jan Kara <jack@suse.cz>
> Signed-off-by: Amir Goldstein <amir73il@gmail.com>
> ---
>  fs/ceph/file.c         |  9 +++---
>  fs/read_write.c        |  6 ++--
>  fs/splice.c            | 71 ++++++++++++++++++++++++++++++------------
>  include/linux/fs.h     |  2 --
>  include/linux/splice.h | 13 +++++---
>  5 files changed, 66 insertions(+), 35 deletions(-)
> 
> diff --git a/fs/ceph/file.c b/fs/ceph/file.c
> index 3b5aae29e944..f11de6e1f1c1 100644
> --- a/fs/ceph/file.c
> +++ b/fs/ceph/file.c
> @@ -12,6 +12,7 @@
>  #include <linux/falloc.h>
>  #include <linux/iversion.h>
>  #include <linux/ktime.h>
> +#include <linux/splice.h>
>  
>  #include "super.h"
>  #include "mds_client.h"
> @@ -3010,8 +3011,8 @@ static ssize_t __ceph_copy_file_range(struct file *src_file, loff_t src_off,
>  		 * {read,write}_iter, which will get caps again.
>  		 */
>  		put_rd_wr_caps(src_ci, src_got, dst_ci, dst_got);
> -		ret = do_splice_direct(src_file, &src_off, dst_file,
> -				       &dst_off, src_objlen, flags);
> +		ret = splice_file_range(src_file, &src_off, dst_file, &dst_off,
> +					src_objlen);
>  		/* Abort on short copies or on error */
>  		if (ret < (long)src_objlen) {
>  			doutc(cl, "Failed partial copy (%zd)\n", ret);
> @@ -3065,8 +3066,8 @@ static ssize_t __ceph_copy_file_range(struct file *src_file, loff_t src_off,
>  	 */
>  	if (len && (len < src_ci->i_layout.object_size)) {
>  		doutc(cl, "Final partial copy of %zu bytes\n", len);
> -		bytes = do_splice_direct(src_file, &src_off, dst_file,
> -					 &dst_off, len, flags);
> +		bytes = splice_file_range(src_file, &src_off, dst_file,
> +					  &dst_off, len);
>  		if (bytes > 0)
>  			ret += bytes;
>  		else
> diff --git a/fs/read_write.c b/fs/read_write.c
> index f791555fa246..642c7ce1ced1 100644
> --- a/fs/read_write.c
> +++ b/fs/read_write.c
> @@ -1423,10 +1423,8 @@ ssize_t generic_copy_file_range(struct file *file_in, loff_t pos_in,
>  				struct file *file_out, loff_t pos_out,
>  				size_t len, unsigned int flags)
>  {
> -	lockdep_assert(file_write_started(file_out));
> -
> -	return do_splice_direct(file_in, &pos_in, file_out, &pos_out,
> -				len > MAX_RW_COUNT ? MAX_RW_COUNT : len, 0);
> +	return splice_file_range(file_in, &pos_in, file_out, &pos_out,
> +				 min_t(size_t, len, MAX_RW_COUNT));
>  }
>  EXPORT_SYMBOL(generic_copy_file_range);
>  
> diff --git a/fs/splice.c b/fs/splice.c
> index 3fce5f6072dd..9007b2c8baa8 100644
> --- a/fs/splice.c
> +++ b/fs/splice.c
> @@ -1170,25 +1170,10 @@ static void direct_file_splice_eof(struct splice_desc *sd)
>  		file->f_op->splice_eof(file);
>  }
>  
> -/**
> - * do_splice_direct - splices data directly between two files
> - * @in:		file to splice from
> - * @ppos:	input file offset
> - * @out:	file to splice to
> - * @opos:	output file offset
> - * @len:	number of bytes to splice
> - * @flags:	splice modifier flags
> - *
> - * Description:
> - *    For use by do_sendfile(). splice can easily emulate sendfile, but
> - *    doing it in the application would incur an extra system call
> - *    (splice in + splice out, as compared to just sendfile()). So this helper
> - *    can splice directly through a process-private pipe.
> - *
> - * Callers already called rw_verify_area() on the entire range.
> - */
> -long do_splice_direct(struct file *in, loff_t *ppos, struct file *out,
> -		      loff_t *opos, size_t len, unsigned int flags)
> +static long do_splice_direct_actor(struct file *in, loff_t *ppos,
> +				   struct file *out, loff_t *opos,
> +				   size_t len, unsigned int flags,
> +				   splice_direct_actor *actor)
>  {
>  	struct splice_desc sd = {
>  		.len		= len,
> @@ -1207,14 +1192,60 @@ long do_splice_direct(struct file *in, loff_t *ppos, struct file *out,
>  	if (unlikely(out->f_flags & O_APPEND))
>  		return -EINVAL;
>  
> -	ret = splice_direct_to_actor(in, &sd, direct_splice_actor);
> +	ret = splice_direct_to_actor(in, &sd, actor);
>  	if (ret > 0)
>  		*ppos = sd.pos;
>  
>  	return ret;
>  }
> +/**
> + * do_splice_direct - splices data directly between two files
> + * @in:		file to splice from
> + * @ppos:	input file offset
> + * @out:	file to splice to
> + * @opos:	output file offset
> + * @len:	number of bytes to splice
> + * @flags:	splice modifier flags
> + *
> + * Description:
> + *    For use by do_sendfile(). splice can easily emulate sendfile, but
> + *    doing it in the application would incur an extra system call
> + *    (splice in + splice out, as compared to just sendfile()). So this helper
> + *    can splice directly through a process-private pipe.
> + *
> + * Callers already called rw_verify_area() on the entire range.
> + */
> +long do_splice_direct(struct file *in, loff_t *ppos, struct file *out,
> +		      loff_t *opos, size_t len, unsigned int flags)
> +{
> +	return do_splice_direct_actor(in, ppos, out, opos, len, flags,
> +				      direct_splice_actor);
> +}
>  EXPORT_SYMBOL(do_splice_direct);
>  
> +/**
> + * splice_file_range - splices data between two files for copy_file_range()
> + * @in:		file to splice from
> + * @ppos:	input file offset
> + * @out:	file to splice to
> + * @opos:	output file offset
> + * @len:	number of bytes to splice
> + *
> + * Description:
> + *    For use by generic_copy_file_range() and ->copy_file_range() methods.
> + *
> + * Callers already called rw_verify_area() on the entire range.
> + */
> +long splice_file_range(struct file *in, loff_t *ppos, struct file *out,
> +		       loff_t *opos, size_t len)
> +{
> +	lockdep_assert(file_write_started(out));
> +
> +	return do_splice_direct_actor(in, ppos, out, opos, len, 0,
> +				      direct_splice_actor);
> +}
> +EXPORT_SYMBOL(splice_file_range);
> +
>  static int wait_for_space(struct pipe_inode_info *pipe, unsigned flags)
>  {
>  	for (;;) {
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index ae0e2fb7bcea..04422a0eccdd 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -3052,8 +3052,6 @@ ssize_t copy_splice_read(struct file *in, loff_t *ppos,
>  			 size_t len, unsigned int flags);
>  extern ssize_t iter_file_splice_write(struct pipe_inode_info *,
>  		struct file *, loff_t *, size_t, unsigned int);
> -extern long do_splice_direct(struct file *in, loff_t *ppos, struct file *out,
> -		loff_t *opos, size_t len, unsigned int flags);
>  
>  
>  extern void
> diff --git a/include/linux/splice.h b/include/linux/splice.h
> index 6c461573434d..49532d5dda52 100644
> --- a/include/linux/splice.h
> +++ b/include/linux/splice.h
> @@ -80,11 +80,14 @@ extern ssize_t add_to_pipe(struct pipe_inode_info *,
>  long vfs_splice_read(struct file *in, loff_t *ppos,
>  		     struct pipe_inode_info *pipe, size_t len,
>  		     unsigned int flags);
> -extern ssize_t splice_direct_to_actor(struct file *, struct splice_desc *,
> -				      splice_direct_actor *);
> -extern long do_splice(struct file *in, loff_t *off_in,
> -		      struct file *out, loff_t *off_out,
> -		      size_t len, unsigned int flags);
> +ssize_t splice_direct_to_actor(struct file *file, struct splice_desc *sd,
> +			       splice_direct_actor *actor);
> +long do_splice(struct file *in, loff_t *off_in, struct file *out,
> +	       loff_t *off_out, size_t len, unsigned int flags);
> +long do_splice_direct(struct file *in, loff_t *ppos, struct file *out,
> +		      loff_t *opos, size_t len, unsigned int flags);
> +long splice_file_range(struct file *in, loff_t *ppos, struct file *out,
> +		       loff_t *opos, size_t len);
>  
>  extern long do_tee(struct file *in, struct file *out, size_t len,
>  		   unsigned int flags);

Looks OK to me:

Acked-by: Jeff Layton <jlayton@kernel.org>
Christoph Hellwig Dec. 4, 2023, 8:37 a.m. UTC | #2
>  		put_rd_wr_caps(src_ci, src_got, dst_ci, dst_got);
> -		ret = do_splice_direct(src_file, &src_off, dst_file,
> -				       &dst_off, src_objlen, flags);
> +		ret = splice_file_range(src_file, &src_off, dst_file, &dst_off,
> +					src_objlen);

Shouldb't ceph be switched to use generic_copy_file_range?
That does the capping of the size which we want, and doesn't update
the file offsets, which would require recalculation in the ceph code.

But this could avoid another exported API as splice_file_range could
simply be folded into generic_copy_file_range which should reduce
confusion.  And splice really is a mess for so many different layers
of the onion being exposed.  I've been wanting to reduce some of that
for a while but haven't found a really nice way yet.
Christoph Hellwig Dec. 4, 2023, 8:38 a.m. UTC | #3
On Mon, Dec 04, 2023 at 09:37:33AM +0100, Christoph Hellwig wrote:
> >  		put_rd_wr_caps(src_ci, src_got, dst_ci, dst_got);
> > -		ret = do_splice_direct(src_file, &src_off, dst_file,
> > -				       &dst_off, src_objlen, flags);
> > +		ret = splice_file_range(src_file, &src_off, dst_file, &dst_off,
> > +					src_objlen);
> 
> Shouldb't ceph be switched to use generic_copy_file_range?
> That does the capping of the size which we want, and doesn't update
> the file offsets, which would require recalculation in the ceph code.
> 
> But this could avoid another exported API as splice_file_range could
> simply be folded into generic_copy_file_range which should reduce
> confusion.  And splice really is a mess for so many different layers
> of the onion being exposed.  I've been wanting to reduce some of that
> for a while but haven't found a really nice way yet.

(and generic_copy_file_range really should be renamed to
splice_copy_file_range and moved to splice.c)
Amir Goldstein Dec. 4, 2023, 1:29 p.m. UTC | #4
On Mon, Dec 4, 2023 at 10:38 AM Christoph Hellwig <hch@lst.de> wrote:
>
> On Mon, Dec 04, 2023 at 09:37:33AM +0100, Christoph Hellwig wrote:
> > >             put_rd_wr_caps(src_ci, src_got, dst_ci, dst_got);
> > > -           ret = do_splice_direct(src_file, &src_off, dst_file,
> > > -                                  &dst_off, src_objlen, flags);
> > > +           ret = splice_file_range(src_file, &src_off, dst_file, &dst_off,
> > > +                                   src_objlen);
> >
> > Shouldn't ceph be switched to use generic_copy_file_range?
> > That does the capping of the size which we want, and doesn't update
> > the file offsets, which would require recalculation in the ceph code.
> >

IDK. I did not want to change the logic of the ceph code.
I am not sure that we must impose MAX_RW_COUNT limit on ceph,
although, i_layout.object_size may already be limited? Jeff?

> > But this could avoid another exported API as splice_file_range could
> > simply be folded into generic_copy_file_range which should reduce
> > confusion.  And splice really is a mess for so many different layers
> > of the onion being exposed.  I've been wanting to reduce some of that
> > for a while but haven't found a really nice way yet.
>
> (and generic_copy_file_range really should be renamed to
> splice_copy_file_range and moved to splice.c)

That depends if we are keeping two helpers.
One with a cap of MAX_RW_COUNT and one without.
If we are going to keep two helpers, I'd rather keep things as they are.
If one helper, then I personally prefer splice_file_range() over
splice_copy_file_range() and other reviewers (Jan) liked this
name as well.

Thanks,
Amir.
Christoph Hellwig Dec. 4, 2023, 2:07 p.m. UTC | #5
On Mon, Dec 04, 2023 at 03:29:43PM +0200, Amir Goldstein wrote:
> > > Shouldn't ceph be switched to use generic_copy_file_range?
> > > That does the capping of the size which we want, and doesn't update
> > > the file offsets, which would require recalculation in the ceph code.
> > >
> 
> IDK. I did not want to change the logic of the ceph code.
> I am not sure that we must impose MAX_RW_COUNT limit on ceph,
> although, i_layout.object_size may already be limited? Jeff?

We better don't go beyond it, as it is called from the copy_file_range
implementation which is expected to never return more than MAX_RW_COUNT.
So either it is a noop change, or it fixes a bug.

> 
> > > But this could avoid another exported API as splice_file_range could
> > > simply be folded into generic_copy_file_range which should reduce
> > > confusion.  And splice really is a mess for so many different layers
> > > of the onion being exposed.  I've been wanting to reduce some of that
> > > for a while but haven't found a really nice way yet.
> >
> > (and generic_copy_file_range really should be renamed to
> > splice_copy_file_range and moved to splice.c)
> 
> That depends if we are keeping two helpers.
> One with a cap of MAX_RW_COUNT and one without.
> If we are going to keep two helpers, I'd rather keep things as they are.
> If one helper, then I personally prefer splice_file_range() over
> splice_copy_file_range() and other reviewers (Jan) liked this
> name as well.

Well, splice_file_range makes sense if it is a separate helper.  But when
is the default implementation for ->copy_file_range and matches the
signature, naming it that way is not only sensible but required to keep
sanity.

> 
> Thanks,
> Amir.
---end quoted text---
Amir Goldstein Dec. 4, 2023, 2:29 p.m. UTC | #6
On Mon, Dec 4, 2023 at 4:07 PM Christoph Hellwig <hch@lst.de> wrote:
>
> On Mon, Dec 04, 2023 at 03:29:43PM +0200, Amir Goldstein wrote:
> > > > Shouldn't ceph be switched to use generic_copy_file_range?
> > > > That does the capping of the size which we want, and doesn't update
> > > > the file offsets, which would require recalculation in the ceph code.
> > > >
> >
> > IDK. I did not want to change the logic of the ceph code.
> > I am not sure that we must impose MAX_RW_COUNT limit on ceph,
> > although, i_layout.object_size may already be limited? Jeff?
>
> We better don't go beyond it, as it is called from the copy_file_range
> implementation which is expected to never return more than MAX_RW_COUNT.
> So either it is a noop change, or it fixes a bug.
>

ok.

> >
> > > > But this could avoid another exported API as splice_file_range could
> > > > simply be folded into generic_copy_file_range which should reduce
> > > > confusion.  And splice really is a mess for so many different layers
> > > > of the onion being exposed.  I've been wanting to reduce some of that
> > > > for a while but haven't found a really nice way yet.
> > >
> > > (and generic_copy_file_range really should be renamed to
> > > splice_copy_file_range and moved to splice.c)
> >
> > That depends if we are keeping two helpers.
> > One with a cap of MAX_RW_COUNT and one without.
> > If we are going to keep two helpers, I'd rather keep things as they are.
> > If one helper, then I personally prefer splice_file_range() over
> > splice_copy_file_range() and other reviewers (Jan) liked this
> > name as well.
>
> Well, splice_file_range makes sense if it is a separate helper.  But when
> is the default implementation for ->copy_file_range and matches the
> signature, naming it that way is not only sensible but required to keep
> sanity.
>

It is NOT a default implementation of ->copy_file_range(), but
a fallback helper.
Specifically, it is never expected to have a filesystem that does
.copy_file_range = generic_copy_file_range,
so getting rid of generic_copy_file_range() would be good.

Note also that generic_copy_file_range() gets a flags argument
that is COPY_FILE_* flags (currently only for the vfs level)
and this flags argument is NOT the splice flags argument, so
I intentionally removed the flags argument from splice_file_range()
to reduce confusion.

I like the idea of moving MAX_RW_COUNT into splice_file_range()
and replacing generic_copy_file_range() calls with splice_file_range().

I do not feel strongly against splice_copy_file_range() name, but
I would like to get feedback from other reviewers that approved the
name splice_file_range() before changing it.

Thanks,
Amir.
Jan Kara Dec. 4, 2023, 5:16 p.m. UTC | #7
On Mon 04-12-23 16:29:02, Amir Goldstein wrote:
> On Mon, Dec 4, 2023 at 4:07 PM Christoph Hellwig <hch@lst.de> wrote:
> > On Mon, Dec 04, 2023 at 03:29:43PM +0200, Amir Goldstein wrote:
> > Well, splice_file_range makes sense if it is a separate helper.  But when
> > is the default implementation for ->copy_file_range and matches the
> > signature, naming it that way is not only sensible but required to keep
> > sanity.
> >
> 
> It is NOT a default implementation of ->copy_file_range(), but
> a fallback helper.
> Specifically, it is never expected to have a filesystem that does
> .copy_file_range = generic_copy_file_range,
> so getting rid of generic_copy_file_range() would be good.
> 
> Note also that generic_copy_file_range() gets a flags argument
> that is COPY_FILE_* flags (currently only for the vfs level)
> and this flags argument is NOT the splice flags argument, so
> I intentionally removed the flags argument from splice_file_range()
> to reduce confusion.
> 
> I like the idea of moving MAX_RW_COUNT into splice_file_range()
> and replacing generic_copy_file_range() calls with splice_file_range().
> 
> I do not feel strongly against splice_copy_file_range() name, but
> I would like to get feedback from other reviewers that approved the
> name splice_file_range() before changing it.

For me the name is not a big deal either way.

								Honza
Amir Goldstein Dec. 4, 2023, 6:53 p.m. UTC | #8
On Mon, Dec 4, 2023 at 7:16 PM Jan Kara <jack@suse.cz> wrote:
>
> On Mon 04-12-23 16:29:02, Amir Goldstein wrote:
> > On Mon, Dec 4, 2023 at 4:07 PM Christoph Hellwig <hch@lst.de> wrote:
> > > On Mon, Dec 04, 2023 at 03:29:43PM +0200, Amir Goldstein wrote:
> > > Well, splice_file_range makes sense if it is a separate helper.  But when
> > > is the default implementation for ->copy_file_range and matches the
> > > signature, naming it that way is not only sensible but required to keep
> > > sanity.
> > >
> >
> > It is NOT a default implementation of ->copy_file_range(), but
> > a fallback helper.
> > Specifically, it is never expected to have a filesystem that does
> > .copy_file_range = generic_copy_file_range,
> > so getting rid of generic_copy_file_range() would be good.
> >
> > Note also that generic_copy_file_range() gets a flags argument
> > that is COPY_FILE_* flags (currently only for the vfs level)
> > and this flags argument is NOT the splice flags argument, so
> > I intentionally removed the flags argument from splice_file_range()
> > to reduce confusion.
> >
> > I like the idea of moving MAX_RW_COUNT into splice_file_range()
> > and replacing generic_copy_file_range() calls with splice_file_range().
> >
> > I do not feel strongly against splice_copy_file_range() name, but
> > I would like to get feedback from other reviewers that approved the
> > name splice_file_range() before changing it.
>
> For me the name is not a big deal either way.

I would rather add this inline wrapper than uniting the two helpers:

static inline long splice_copy_file_range(struct file *in, loff_t pos_in,
                                          struct file *out, loff_t pos_out,
                                          size_t len)
{
        return splice_file_range(in, &pos_in, out, &pos_out,
                                      min_t(size_t, len, MAX_RW_COUNT));
}

It is keeping the same signature as copy_file_range() minus flags,
to be used for ->copy_file_range() fallback and keeps the current
splice_file_range() helpers for special cases as ceph that want to
update the in/out offset.

Thanks,
Amir.
diff mbox series

Patch

diff --git a/fs/ceph/file.c b/fs/ceph/file.c
index 3b5aae29e944..f11de6e1f1c1 100644
--- a/fs/ceph/file.c
+++ b/fs/ceph/file.c
@@ -12,6 +12,7 @@ 
 #include <linux/falloc.h>
 #include <linux/iversion.h>
 #include <linux/ktime.h>
+#include <linux/splice.h>
 
 #include "super.h"
 #include "mds_client.h"
@@ -3010,8 +3011,8 @@  static ssize_t __ceph_copy_file_range(struct file *src_file, loff_t src_off,
 		 * {read,write}_iter, which will get caps again.
 		 */
 		put_rd_wr_caps(src_ci, src_got, dst_ci, dst_got);
-		ret = do_splice_direct(src_file, &src_off, dst_file,
-				       &dst_off, src_objlen, flags);
+		ret = splice_file_range(src_file, &src_off, dst_file, &dst_off,
+					src_objlen);
 		/* Abort on short copies or on error */
 		if (ret < (long)src_objlen) {
 			doutc(cl, "Failed partial copy (%zd)\n", ret);
@@ -3065,8 +3066,8 @@  static ssize_t __ceph_copy_file_range(struct file *src_file, loff_t src_off,
 	 */
 	if (len && (len < src_ci->i_layout.object_size)) {
 		doutc(cl, "Final partial copy of %zu bytes\n", len);
-		bytes = do_splice_direct(src_file, &src_off, dst_file,
-					 &dst_off, len, flags);
+		bytes = splice_file_range(src_file, &src_off, dst_file,
+					  &dst_off, len);
 		if (bytes > 0)
 			ret += bytes;
 		else
diff --git a/fs/read_write.c b/fs/read_write.c
index f791555fa246..642c7ce1ced1 100644
--- a/fs/read_write.c
+++ b/fs/read_write.c
@@ -1423,10 +1423,8 @@  ssize_t generic_copy_file_range(struct file *file_in, loff_t pos_in,
 				struct file *file_out, loff_t pos_out,
 				size_t len, unsigned int flags)
 {
-	lockdep_assert(file_write_started(file_out));
-
-	return do_splice_direct(file_in, &pos_in, file_out, &pos_out,
-				len > MAX_RW_COUNT ? MAX_RW_COUNT : len, 0);
+	return splice_file_range(file_in, &pos_in, file_out, &pos_out,
+				 min_t(size_t, len, MAX_RW_COUNT));
 }
 EXPORT_SYMBOL(generic_copy_file_range);
 
diff --git a/fs/splice.c b/fs/splice.c
index 3fce5f6072dd..9007b2c8baa8 100644
--- a/fs/splice.c
+++ b/fs/splice.c
@@ -1170,25 +1170,10 @@  static void direct_file_splice_eof(struct splice_desc *sd)
 		file->f_op->splice_eof(file);
 }
 
-/**
- * do_splice_direct - splices data directly between two files
- * @in:		file to splice from
- * @ppos:	input file offset
- * @out:	file to splice to
- * @opos:	output file offset
- * @len:	number of bytes to splice
- * @flags:	splice modifier flags
- *
- * Description:
- *    For use by do_sendfile(). splice can easily emulate sendfile, but
- *    doing it in the application would incur an extra system call
- *    (splice in + splice out, as compared to just sendfile()). So this helper
- *    can splice directly through a process-private pipe.
- *
- * Callers already called rw_verify_area() on the entire range.
- */
-long do_splice_direct(struct file *in, loff_t *ppos, struct file *out,
-		      loff_t *opos, size_t len, unsigned int flags)
+static long do_splice_direct_actor(struct file *in, loff_t *ppos,
+				   struct file *out, loff_t *opos,
+				   size_t len, unsigned int flags,
+				   splice_direct_actor *actor)
 {
 	struct splice_desc sd = {
 		.len		= len,
@@ -1207,14 +1192,60 @@  long do_splice_direct(struct file *in, loff_t *ppos, struct file *out,
 	if (unlikely(out->f_flags & O_APPEND))
 		return -EINVAL;
 
-	ret = splice_direct_to_actor(in, &sd, direct_splice_actor);
+	ret = splice_direct_to_actor(in, &sd, actor);
 	if (ret > 0)
 		*ppos = sd.pos;
 
 	return ret;
 }
+/**
+ * do_splice_direct - splices data directly between two files
+ * @in:		file to splice from
+ * @ppos:	input file offset
+ * @out:	file to splice to
+ * @opos:	output file offset
+ * @len:	number of bytes to splice
+ * @flags:	splice modifier flags
+ *
+ * Description:
+ *    For use by do_sendfile(). splice can easily emulate sendfile, but
+ *    doing it in the application would incur an extra system call
+ *    (splice in + splice out, as compared to just sendfile()). So this helper
+ *    can splice directly through a process-private pipe.
+ *
+ * Callers already called rw_verify_area() on the entire range.
+ */
+long do_splice_direct(struct file *in, loff_t *ppos, struct file *out,
+		      loff_t *opos, size_t len, unsigned int flags)
+{
+	return do_splice_direct_actor(in, ppos, out, opos, len, flags,
+				      direct_splice_actor);
+}
 EXPORT_SYMBOL(do_splice_direct);
 
+/**
+ * splice_file_range - splices data between two files for copy_file_range()
+ * @in:		file to splice from
+ * @ppos:	input file offset
+ * @out:	file to splice to
+ * @opos:	output file offset
+ * @len:	number of bytes to splice
+ *
+ * Description:
+ *    For use by generic_copy_file_range() and ->copy_file_range() methods.
+ *
+ * Callers already called rw_verify_area() on the entire range.
+ */
+long splice_file_range(struct file *in, loff_t *ppos, struct file *out,
+		       loff_t *opos, size_t len)
+{
+	lockdep_assert(file_write_started(out));
+
+	return do_splice_direct_actor(in, ppos, out, opos, len, 0,
+				      direct_splice_actor);
+}
+EXPORT_SYMBOL(splice_file_range);
+
 static int wait_for_space(struct pipe_inode_info *pipe, unsigned flags)
 {
 	for (;;) {
diff --git a/include/linux/fs.h b/include/linux/fs.h
index ae0e2fb7bcea..04422a0eccdd 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -3052,8 +3052,6 @@  ssize_t copy_splice_read(struct file *in, loff_t *ppos,
 			 size_t len, unsigned int flags);
 extern ssize_t iter_file_splice_write(struct pipe_inode_info *,
 		struct file *, loff_t *, size_t, unsigned int);
-extern long do_splice_direct(struct file *in, loff_t *ppos, struct file *out,
-		loff_t *opos, size_t len, unsigned int flags);
 
 
 extern void
diff --git a/include/linux/splice.h b/include/linux/splice.h
index 6c461573434d..49532d5dda52 100644
--- a/include/linux/splice.h
+++ b/include/linux/splice.h
@@ -80,11 +80,14 @@  extern ssize_t add_to_pipe(struct pipe_inode_info *,
 long vfs_splice_read(struct file *in, loff_t *ppos,
 		     struct pipe_inode_info *pipe, size_t len,
 		     unsigned int flags);
-extern ssize_t splice_direct_to_actor(struct file *, struct splice_desc *,
-				      splice_direct_actor *);
-extern long do_splice(struct file *in, loff_t *off_in,
-		      struct file *out, loff_t *off_out,
-		      size_t len, unsigned int flags);
+ssize_t splice_direct_to_actor(struct file *file, struct splice_desc *sd,
+			       splice_direct_actor *actor);
+long do_splice(struct file *in, loff_t *off_in, struct file *out,
+	       loff_t *off_out, size_t len, unsigned int flags);
+long do_splice_direct(struct file *in, loff_t *ppos, struct file *out,
+		      loff_t *opos, size_t len, unsigned int flags);
+long splice_file_range(struct file *in, loff_t *ppos, struct file *out,
+		       loff_t *opos, size_t len);
 
 extern long do_tee(struct file *in, struct file *out, size_t len,
 		   unsigned int flags);