Browse Source

io/fdpass: new module for async fd passing.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Rusty Russell 9 years ago
parent
commit
05b15e51d1
5 changed files with 269 additions and 0 deletions
  1. 1 0
      ccan/io/fdpass/LICENSE
  2. 95 0
      ccan/io/fdpass/_info
  3. 54 0
      ccan/io/fdpass/fdpass.c
  4. 68 0
      ccan/io/fdpass/fdpass.h
  5. 51 0
      ccan/io/fdpass/test/run.c

+ 1 - 0
ccan/io/fdpass/LICENSE

@@ -0,0 +1 @@
+../../../licenses/LGPL-2.1

+ 95 - 0
ccan/io/fdpass/_info

@@ -0,0 +1,95 @@
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+
+/**
+ * io/fdpass - IO helper for passing file descriptors across local sockets
+ *
+ * This code adds the ability to pass file descriptors to ccan/io.
+ *
+ * License: LGPL (v2.1 or any later version)
+ * Author: Rusty Russell <rusty@rustcorp.com.au>
+ *
+ * Example:
+ *	// Given "hello" outputs hello
+ *	#include <ccan/io/fdpass/fdpass.h>
+ *	#include <sys/types.h>
+ *	#include <sys/socket.h>
+ *	#include <sys/un.h>
+ *	#include <stdio.h>
+ *	#include <stdlib.h>
+ *	#include <unistd.h>
+ *
+ *	// Child reads stdin into the buffer, prints it out.
+ *	struct buf {
+ *		size_t used;
+ *		char c[100];
+ *	};
+ *	static struct io_plan *read_more(struct io_conn *conn, struct buf *buf)
+ *	{
+ *		printf("%.*s", (int)buf->used, buf->c);
+ *		return io_read_partial(conn, buf->c, sizeof(buf->c), &buf->used,
+ *					read_more, buf);
+ *	}
+ *
+ *	// Child has received fd, start reading loop.
+ *	static struct io_plan *got_infd(struct io_conn *conn, int *infd)
+ *	{
+ *		struct buf *buf = calloc(1, sizeof(*buf));
+ *
+ *		io_new_conn(NULL, *infd, read_more, buf);
+ *		return io_close(conn);
+ *	}
+ *	// Child is receiving the fd to read into. 
+ *	static struct io_plan *recv_infd(struct io_conn *conn, int *infd)
+ *	{
+ *		return io_recv_fd(conn, infd, got_infd, infd);
+ *	}
+ *
+ *	// Gets passed fd (stdin), which it reads from.
+ *	static void child(int sockfd)
+ *	{
+ *		int infd;
+ *
+ *		io_new_conn(NULL, sockfd, recv_infd, &infd);
+ *		io_loop(NULL, NULL);
+ *		exit(0);
+ *	}
+ *
+ *	static struct io_plan *send_stdin(struct io_conn *conn, void *unused)
+ *	{
+ *		return io_send_fd(conn, STDIN_FILENO, io_close_cb, NULL);
+ *	}
+ *
+ *	static void parent(int sockfd)
+ *	{
+ *		io_new_conn(NULL, sockfd, send_stdin, NULL);
+ *		io_loop(NULL, NULL);
+ *		exit(0);
+ *	}
+ *	
+ *	int main(void)
+ *	{
+ *		int sv[2];
+ *	
+ *		socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
+ *		if (fork() == 0)
+ *			child(sv[0]);
+ *		else
+ *			parent(sv[1]);
+ *	}
+ */
+int main(int argc, char *argv[])
+{
+	/* Expect exactly one argument */
+	if (argc != 2)
+		return 1;
+
+	if (strcmp(argv[1], "depends") == 0) {
+		printf("ccan/fdpass\n");
+		printf("ccan/io\n");
+		return 0;
+	}
+
+	return 1;
+}

+ 54 - 0
ccan/io/fdpass/fdpass.c

@@ -0,0 +1,54 @@
+/* GNU LGPL version 2 (or later) - see LICENSE file for details */
+#include <ccan/io/fdpass/fdpass.h>
+#include <ccan/fdpass/fdpass.h>
+#include <ccan/io/io_plan.h>
+#include <errno.h>
+
+static int do_fd_send(int fd, struct io_plan_arg *arg)
+{
+	if (!fdpass_send(fd, arg->u1.s)) {
+		/* In case ccan/io ever gets smart with non-blocking. */
+		if (errno == EAGAIN || errno == EWOULDBLOCK)
+			return 0;
+		return -1;
+	}
+	return 1;
+}
+
+struct io_plan *io_send_fd_(struct io_conn *conn,
+			    int fd,
+			    struct io_plan *(*next)(struct io_conn *, void *),
+			    void *next_arg)
+{
+	struct io_plan_arg *arg = io_plan_arg(conn, IO_OUT);
+
+	arg->u1.s = fd;
+
+	return io_set_plan(conn, IO_OUT, do_fd_send, next, next_arg);
+}
+
+static int do_fd_recv(int fd, struct io_plan_arg *arg)
+{
+	int fdin = fdpass_recv(fd);
+
+	if (fdin < 0) {
+		/* In case ccan/io ever gets smart with non-blocking. */
+		if (errno == EAGAIN || errno == EWOULDBLOCK)
+			return 0;
+		return -1;
+	}
+	*(int *)arg->u1.vp = fdin;
+	return 1;
+}
+
+struct io_plan *io_recv_fd_(struct io_conn *conn,
+			    int *fd,
+			    struct io_plan *(*next)(struct io_conn *, void *),
+			    void *next_arg)
+{
+	struct io_plan_arg *arg = io_plan_arg(conn, IO_IN);
+
+	arg->u1.vp = fd;
+
+	return io_set_plan(conn, IO_IN, do_fd_recv, next, next_arg);
+}

+ 68 - 0
ccan/io/fdpass/fdpass.h

@@ -0,0 +1,68 @@
+/* GNU LGPL version 2 (or later) - see LICENSE file for details */
+#ifndef CCAN_IO_FDPASS_H
+#define CCAN_IO_FDPASS_H
+#include <ccan/io/io.h>
+
+/**
+ * io_send_fd - output plan to send a file descriptor
+ * @conn: the connection that plan is for.
+ * @fd: the file descriptor to pass.
+ * @next: function to call output is done.
+ * @arg: @next argument
+ *
+ * This updates the output plan, to write out a file descriptor.  This
+ * usually only works over an AF_LOCAL (ie. Unix domain) socket.  Once
+ * that's sent, the @next function will be called: on an error, the
+ * finish function is called instead.
+ *
+ * Note that the I/O may actually be done immediately, and the other end
+ * of the socket must use io_recv_fd: if it does a normal read, the file
+ * descriptor will be lost.
+ *
+ * Example:
+ * static struct io_plan *fd_to_conn(struct io_conn *conn, int fd)
+ * {
+ *	// Write fd, then close.
+ *	return io_send_fd(conn, fd, io_close_cb, NULL);
+ * }
+ */
+#define io_send_fd(conn, fd, next, arg)					\
+	io_send_fd_((conn), (fd),					\
+		    typesafe_cb_preargs(struct io_plan *, void *,	\
+					(next), (arg), struct io_conn *), \
+		    (arg))
+struct io_plan *io_send_fd_(struct io_conn *conn,
+			    int fd,
+			    struct io_plan *(*next)(struct io_conn *, void *),
+			    void *arg);
+
+/**
+ * io_recv_fd - input plan to receive a file descriptor
+ * @conn: the connection that plan is for.
+ * @fd: a pointer to where to place to file descriptor
+ * @next: function to call once input is done.
+ * @arg: @next argument
+ *
+ * This creates a plan to receive a file descriptor, as sent by
+ * io_send_fd.  Once it's all read, the @next function will be called:
+ * on an error, the finish function is called instead.
+ *
+ * Note that the I/O may actually be done immediately.
+ *
+ * Example:
+ * static struct io_plan *read_from_conn(struct io_conn *conn, int *fdp)
+ * {
+ *	// Read message, then close.
+ *	return io_recv_fd(conn, fdp, io_close_cb, NULL);
+ * }
+ */
+#define io_recv_fd(conn, fd, next, arg)					\
+	io_recv_fd_((conn), (fd),					\
+		    typesafe_cb_preargs(struct io_plan *, void *,	\
+					(next), (arg), struct io_conn *), \
+		    (arg))
+struct io_plan *io_recv_fd_(struct io_conn *conn,
+			    int *fd,
+			    struct io_plan *(*next)(struct io_conn *, void *),
+			    void *arg);
+#endif /* CCAN_IO_FDPASS_H */

+ 51 - 0
ccan/io/fdpass/test/run.c

@@ -0,0 +1,51 @@
+#include <ccan/io/fdpass/fdpass.h>
+/* Include the C files directly. */
+#include <ccan/io/fdpass/fdpass.c>
+#include <ccan/tap/tap.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+static struct io_plan *try_reading(struct io_conn *conn, int *fd)
+{
+	char buf[6];
+	ok1(read(*fd, buf, sizeof(buf)) == sizeof(buf));
+	ok1(memcmp(buf, "hello!", sizeof(buf)) == 0);
+	return io_close(conn);
+}
+
+static struct io_plan *get_fd(struct io_conn *conn, void *unused)
+{
+	int *fd = tal(conn, int);
+	return io_recv_fd(conn, fd, try_reading, fd);
+}
+
+static struct io_plan *try_writing(struct io_conn *conn, int *pfd)
+{
+	close(pfd[0]);
+	ok1(write(pfd[1], "hello!", 6) == 6);
+	return io_close(conn);
+}
+
+static struct io_plan *send_fd(struct io_conn *conn, int *pfd)
+{
+	return io_send_fd(conn, pfd[0], try_writing, pfd);
+}
+
+int main(void)
+{
+	int sv[2];
+	int pfd[2];
+
+	plan_tests(5);
+	ok1(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == 0);
+	ok1(pipe(pfd) == 0);
+
+	/* Pass read end of pipe to ourselves, test. */
+	io_new_conn(NULL, sv[0], get_fd, NULL);
+	io_new_conn(NULL, sv[1], send_fd, pfd);
+
+	io_loop(NULL, NULL);
+
+	/* This exits depending on whether all tests passed */
+	return exit_status();
+}