sendfile() is implemented by performing an internal "direct" splice between two regular files. A per-task pipe buffer is allocated to splice between the reads from the source page cache and writes to the destination file page cache. This patch lets userspace perform these direct splices with sys_splice() by setting the SPLICE_F_DIRECT flag. This provides a single syscall for copying a region between files without either having to store the destination offset in the descriptor for sendfile or having to use multiple splicing syscalls to and from a pipe. Providing both files to the method lets the file system lock both for the duration of the copy, should it need to. If the method refuses to accelerate the copy, for whatever reason, we can naturally fall back to the generic direct splice method that sendfile uses today. Signed-off-by: Zach Brown <zab@xxxxxxxxxx> --- fs/splice.c | 38 ++++++++++++++++++++++++++++++++++++-- include/linux/splice.h | 1 + 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/fs/splice.c b/fs/splice.c index 3b7ee65..c0f4e27 100644 --- a/fs/splice.c +++ b/fs/splice.c @@ -1347,7 +1347,7 @@ static long do_splice(struct file *in, loff_t __user *off_in, } if (ipipe) { - if (off_in) + if (off_in || (flags & SPLICE_F_DIRECT)) return -ESPIPE; if (off_out) { if (!(out->f_mode & FMODE_PWRITE)) @@ -1381,7 +1381,7 @@ static long do_splice(struct file *in, loff_t __user *off_in, } if (opipe) { - if (off_out) + if (off_out || (flags & SPLICE_F_DIRECT)) return -ESPIPE; if (off_in) { if (!(in->f_mode & FMODE_PREAD)) @@ -1402,6 +1402,40 @@ static long do_splice(struct file *in, loff_t __user *off_in, return ret; } + if (flags & SPLICE_F_DIRECT) { + loff_t out_pos; + + if (off_in) { + if (!(in->f_mode & FMODE_PREAD)) + return -EINVAL; + if (copy_from_user(&offset, off_in, sizeof(loff_t))) + return -EFAULT; + } else + offset = in->f_pos; + + if (off_out) { + if (!(out->f_mode & FMODE_PWRITE)) + return -EINVAL; + if (copy_from_user(&out_pos, off_out, sizeof(loff_t))) + return -EFAULT; + } else + out_pos = out->f_pos; + + ret = do_splice_direct(in, &offset, out, &out_pos, len, flags); + + if (!off_in) + in->f_pos = offset; + else if (copy_to_user(off_in, &offset, sizeof(loff_t))) + ret = -EFAULT; + + if (!off_out) + out->f_pos = out_pos; + else if (copy_to_user(off_out, &out_pos, sizeof(loff_t))) + ret = -EFAULT; + + return ret; + } + return -EINVAL; } diff --git a/include/linux/splice.h b/include/linux/splice.h index 74575cb..e1aa3ad 100644 --- a/include/linux/splice.h +++ b/include/linux/splice.h @@ -19,6 +19,7 @@ /* from/to, of course */ #define SPLICE_F_MORE (0x04) /* expect more data */ #define SPLICE_F_GIFT (0x08) /* pages passed in are a gift */ +#define SPLICE_F_DIRECT (0x10) /* neither splice fd is a pipe */ /* * Passed to the actors -- 1.7.11.7 -- To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html