Commit 05b15e51 authored by Rusty Russell's avatar Rusty Russell

io/fdpass: new module for async fd passing.

Signed-off-by: default avatarRusty Russell <rusty@rustcorp.com.au>
parent 4c8b47ff
../../../licenses/LGPL-2.1
\ No newline at end of file
#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;
}
/* 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);
}
/* 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 */
#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();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment