lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:	Thu, 30 Apr 2009 14:03:54 +0200
From:	Max Kellermann <mk@...all.com>
To:	linux-kernel@...r.kernel.org
Cc:	jens.axboe@...cle.com, w@....eu
Subject: [splice PATCH 3/3] splice: added support for pipe-to-pipe splice()

This patch enables the splice() system call to copy buffers from one
pipe to another.  This obvious and trivial use case for splice() was
not supported until now.

It reuses the functions link_ipipe_prep() and link_opipe_prep() from
the tee() system call implementation.

Signed-off-by: Max Kellermann <mk@...all.com>
---

 fs/splice.c |  166 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 166 insertions(+), 0 deletions(-)

diff --git a/fs/splice.c b/fs/splice.c
index 96135eb..f69a88f 100644
--- a/fs/splice.c
+++ b/fs/splice.c
@@ -902,6 +902,156 @@ ssize_t generic_splice_sendpage(struct pipe_inode_info *pipe, struct file *out,
 
 EXPORT_SYMBOL(generic_splice_sendpage);
 
+static int link_ipipe_prep(struct pipe_inode_info *pipe, unsigned int flags);
+static int link_opipe_prep(struct pipe_inode_info *pipe, unsigned int flags);
+
+/**
+ * Returns the nth pipe buffer after the current one.
+ *
+ * @i the buffer index, relative to the current one
+ */
+static inline struct pipe_buffer *
+pipe_buffer_at(struct pipe_inode_info *pipe, unsigned i)
+{
+	BUG_ON(i >= PIPE_BUFFERS);
+
+	return pipe->bufs + ((pipe->curbuf + i) & (PIPE_BUFFERS - 1));
+}
+
+/**
+ * Splice pages from one pipe to another.
+ *
+ * @ipipe the input pipe
+ * @opipe the output pipe
+ * @len the maximum number of bytes to move
+ * @flags splice modifier flags
+ */
+static long do_splice_pipes(struct pipe_inode_info *ipipe,
+			    struct pipe_inode_info *opipe,
+			    size_t len, unsigned int flags)
+{
+	struct pipe_buffer *ibuf, *obuf;
+	long ret;
+	int do_wakeup = 0;
+
+	if (ipipe == opipe)
+		/* cannot splice a pipe to itself */
+		return -EINVAL;
+
+	while (1) {
+		/* wait for ipipe to become ready to read */
+		ret = link_ipipe_prep(ipipe, flags);
+		if (ret)
+			return ret;
+
+		/* wait for opipe to become ready to write */
+		ret = link_opipe_prep(opipe, flags);
+		if (ret)
+			return ret;
+
+		/* lock both pipes */
+		pipe_double_lock(ipipe, opipe);
+
+		/* see if the splice() is still possible */
+		if ((ipipe->nrbufs > 0 || ipipe->writers == 0) &&
+		    opipe->nrbufs < PIPE_BUFFERS)
+			/* yes, it is - keep the locks and end this
+			   loop */
+			break;
+
+		/* no - someone has drained ipipe or has filled opipe
+		   between link_[io]pipe_pre()'s lock and our lock.
+		   Drop both locks and wait again. */
+		pipe_unlock(ipipe);
+		pipe_unlock(opipe);
+	}
+
+	do {
+		if (opipe->readers == 0) {
+			/* nobody's reading from the output pipe: send
+			   SIGPIPE */
+			send_sig(SIGPIPE, current, 0);
+			if (!ret)
+				ret = -EPIPE;
+			break;
+		}
+
+		/*
+		 * If we have iterated all input buffers or ran out of
+		 * output room, break.
+		 */
+		if (ipipe->nrbufs == 0 || opipe->nrbufs >= PIPE_BUFFERS)
+			break;
+
+		/* now do the real thing: move a buffer (or a part of
+		   it) from ipipe to opipe */
+
+		ibuf = pipe_buffer_at(ipipe, 0);
+		obuf = pipe_buffer_at(opipe, opipe->nrbufs);
+		*obuf = *ibuf;
+
+		/*
+		 * Don't inherit the gift flag, we need to
+		 * prevent multiple steals of this page.
+		 */
+		obuf->flags &= ~PIPE_BUF_FLAG_GIFT;
+
+		/* increase the reference counter */
+		obuf->ops->get(opipe, obuf);
+
+		if (obuf->len > len) {
+			/* partial move */
+
+			obuf->len = len;
+
+			/* remove the portion from ibuf */
+			ibuf->offset += len;
+			ibuf->len -= len;
+		} else {
+			/* full move: remove buffer from the input
+			   pipe */
+
+			ibuf->ops->release(ipipe, ibuf);
+			ibuf->ops = NULL;
+
+			ipipe->curbuf = (ipipe->curbuf + 1) &
+				(PIPE_BUFFERS - 1);
+			ipipe->nrbufs--;
+
+			do_wakeup = 1;
+		}
+
+		opipe->nrbufs++;
+		ret += obuf->len;
+		len -= obuf->len;
+	} while (len > 0);
+
+	pipe_unlock(ipipe);
+	pipe_unlock(opipe);
+
+	if (do_wakeup) {
+		/* at least one buffer was removed from the
+		   input pipe: wake up potential writers */
+		smp_mb();
+		if (waitqueue_active(&ipipe->wait))
+			wake_up_interruptible(&ipipe->wait);
+		kill_fasync(&ipipe->fasync_writers, SIGIO, POLL_OUT);
+	}
+
+	/*
+	 * If we put data in the output pipe, wakeup any potential
+	 * readers.
+	 */
+	if (ret > 0) {
+		smp_mb();
+		if (waitqueue_active(&opipe->wait))
+			wake_up_interruptible(&opipe->wait);
+		kill_fasync(&opipe->fasync_readers, SIGIO, POLL_IN);
+	}
+
+	return ret;
+}
+
 /*
  * Attempt to initiate a splice from pipe to file.
  */
@@ -1138,8 +1288,24 @@ static long do_splice(struct file *in, loff_t __user *off_in,
 
 	pipe = pipe_info(in->f_path.dentry->d_inode);
 	if (pipe) {
+		struct pipe_inode_info *out_pipe;
+
 		if (off_in)
 			return -ESPIPE;
+
+		out_pipe = pipe_info(out->f_path.dentry->d_inode);
+		if (out_pipe) {
+			/* special case: a splice between two pipes */
+
+			if (unlikely(!(out->f_mode & FMODE_WRITE)))
+				return -EBADF;
+
+			if (off_out)
+				return -ESPIPE;
+
+			return do_splice_pipes(pipe, out_pipe, len, flags);
+		}
+
 		if (off_out) {
 			if (out->f_op->llseek == no_llseek)
 				return -EINVAL;

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ