From: Rusty Russell Date: Fri, 23 Dec 2016 00:40:57 +0000 (+1030) Subject: io/fdpass: new module for async fd passing. X-Git-Url: https://git.ozlabs.org/?p=ccan;a=commitdiff_plain;h=05b15e51d1312f0b2970061b6497325d43f2be02 io/fdpass: new module for async fd passing. Signed-off-by: Rusty Russell --- diff --git a/ccan/io/fdpass/LICENSE b/ccan/io/fdpass/LICENSE new file mode 120000 index 00000000..4d0b239e --- /dev/null +++ b/ccan/io/fdpass/LICENSE @@ -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 index 00000000..406b2f5c --- /dev/null +++ b/ccan/io/fdpass/_info @@ -0,0 +1,95 @@ +#include "config.h" +#include +#include + +/** + * 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 + * + * Example: + * // Given "hello" outputs hello + * #include + * #include + * #include + * #include + * #include + * #include + * #include + * + * // 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 index 00000000..11208a9b --- /dev/null +++ b/ccan/io/fdpass/fdpass.c @@ -0,0 +1,54 @@ +/* GNU LGPL version 2 (or later) - see LICENSE file for details */ +#include +#include +#include +#include + +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 index 00000000..366ff352 --- /dev/null +++ b/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 + +/** + * 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 index 00000000..d2021e82 --- /dev/null +++ b/ccan/io/fdpass/test/run.c @@ -0,0 +1,51 @@ +#include +/* Include the C files directly. */ +#include +#include +#include +#include + +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(); +}