io/fdpass: new module for async fd passing.
authorRusty Russell <rusty@rustcorp.com.au>
Fri, 23 Dec 2016 00:40:57 +0000 (11:10 +1030)
committerRusty Russell <rusty@rustcorp.com.au>
Fri, 23 Dec 2016 00:40:57 +0000 (11:10 +1030)
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
ccan/io/fdpass/LICENSE [new symlink]
ccan/io/fdpass/_info [new file with mode: 0644]
ccan/io/fdpass/fdpass.c [new file with mode: 0644]
ccan/io/fdpass/fdpass.h [new file with mode: 0644]
ccan/io/fdpass/test/run.c [new file with mode: 0644]

diff --git a/ccan/io/fdpass/LICENSE b/ccan/io/fdpass/LICENSE
new file mode 120000 (symlink)
index 0000000..4d0b239
--- /dev/null
@@ -0,0 +1 @@
+../../../licenses/LGPL-2.1
\ No newline at end of file
diff --git a/ccan/io/fdpass/_info b/ccan/io/fdpass/_info
new file mode 100644 (file)
index 0000000..406b2f5
--- /dev/null
@@ -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;
+}
diff --git a/ccan/io/fdpass/fdpass.c b/ccan/io/fdpass/fdpass.c
new file mode 100644 (file)
index 0000000..11208a9
--- /dev/null
@@ -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);
+}
diff --git a/ccan/io/fdpass/fdpass.h b/ccan/io/fdpass/fdpass.h
new file mode 100644 (file)
index 0000000..366ff35
--- /dev/null
@@ -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 */
diff --git a/ccan/io/fdpass/test/run.c b/ccan/io/fdpass/test/run.c
new file mode 100644 (file)
index 0000000..d2021e8
--- /dev/null
@@ -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();
+}