From b85c47bb81a9078afc5ddc51448560187348bbbf Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 14 Oct 2013 21:33:07 +1030 Subject: [PATCH 1/1] ccan/io: io_connect() Not a perfect solution (we'd ideally want to go to another plan immediately, but that would involve a malloc). Signed-off-by: Rusty Russell --- ccan/io/io.c | 55 +++++++++++++++ ccan/io/io.h | 25 +++++++ ccan/io/test/run-09-connect-DEBUG.c | 8 +++ ccan/io/test/run-09-connect.c | 103 ++++++++++++++++++++++++++++ 4 files changed, 191 insertions(+) create mode 100644 ccan/io/test/run-09-connect-DEBUG.c create mode 100644 ccan/io/test/run-09-connect.c diff --git a/ccan/io/io.c b/ccan/io/io.c index ddcd502f..76f1f441 100644 --- a/ccan/io/io.c +++ b/ccan/io/io.c @@ -9,6 +9,8 @@ #include #include #include +#include +#include void *io_loop_return; @@ -337,6 +339,59 @@ struct io_plan io_write_partial_(const void *data, size_t *len, return plan; } +static int already_connected(int fd, struct io_plan *plan) +{ + return io_debug_io(1); +} + +static int do_connect(int fd, struct io_plan *plan) +{ + int err, ret; + socklen_t len = sizeof(err); + + /* Has async connect finished? */ + ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len); + if (ret < 0) + return -1; + + if (err == 0) { + /* Restore blocking if it was initially. */ + fcntl(fd, F_SETFD, plan->u.len_len.len1); + return 1; + } + return 0; +} + +struct io_plan io_connect_(int fd, const struct addrinfo *addr, + struct io_plan (*cb)(struct io_conn*, void *), + void *arg) +{ + struct io_plan plan; + + assert(cb); + + plan.next = cb; + plan.next_arg = arg; + + /* Save old flags, set nonblock if not already. */ + plan.u.len_len.len1 = fcntl(fd, F_GETFD); + fcntl(fd, F_SETFD, plan.u.len_len.len1 | O_NONBLOCK); + + /* Immediate connect can happen. */ + if (connect(fd, addr->ai_addr, addr->ai_addrlen) == 0) { + /* Dummy will be called immediately. */ + plan.pollflag = POLLOUT; + plan.io = already_connected; + } else { + if (errno != EINPROGRESS) + return io_close_(); + + plan.pollflag = POLLIN; + plan.io = do_connect; + } + return plan; +} + struct io_plan io_idle_(void) { struct io_plan plan; diff --git a/ccan/io/io.h b/ccan/io/io.h index c85a5b8c..772db929 100644 --- a/ccan/io/io.h +++ b/ccan/io/io.h @@ -160,6 +160,31 @@ struct io_plan io_write_partial_(const void *data, size_t *len, struct io_plan (*cb)(struct io_conn *, void*), void *arg); +/** + * io_connect - plan to connect to a listening socket. + * @fd: file descriptor. + * @addr: where to connect. + * @cb: function to call once it's done. + * @arg: @cb argument + * + * This initiates a connection, and creates a plan for + * (asynchronously). completing it. Once complete, @len is updated + * and the @cb function will be called: on an error, the finish + * function is called instead. + * + * Note that the connect may actually be done immediately. + */ +struct addrinfo; +#define io_connect(fd, addr, cb, arg) \ + io_debug(io_connect_((fd), (addr), \ + typesafe_cb_preargs(struct io_plan, void *, \ + (cb), (arg), \ + struct io_conn *), \ + (arg))) +struct io_plan io_connect_(int fd, const struct addrinfo *addr, + struct io_plan (*cb)(struct io_conn *, void*), + void *arg); + /** * io_idle - plan to do nothing. * diff --git a/ccan/io/test/run-09-connect-DEBUG.c b/ccan/io/test/run-09-connect-DEBUG.c new file mode 100644 index 00000000..5520dd78 --- /dev/null +++ b/ccan/io/test/run-09-connect-DEBUG.c @@ -0,0 +1,8 @@ +#define DEBUG +#define PORT "64009" +#define main real_main +int real_main(void); +#include "run-09-connect.c" +#undef main +static bool always_debug(struct io_conn *conn) { return true; } +int main(void) { io_debug_conn = always_debug; return real_main(); } diff --git a/ccan/io/test/run-09-connect.c b/ccan/io/test/run-09-connect.c new file mode 100644 index 00000000..fd7e1606 --- /dev/null +++ b/ccan/io/test/run-09-connect.c @@ -0,0 +1,103 @@ +#include +/* Include the C files directly. */ +#include +#include +#include +#include +#include + +#ifndef PORT +#define PORT "65009" +#endif + +static struct io_listener *l; + +struct data { + int state; + char buf[10]; +}; + +static struct io_plan closer(struct io_conn *conn, struct data *d) +{ + d->state++; + return io_close(); +} + +static struct io_plan connected(struct io_conn *conn, struct data *d2) +{ + ok1(d2->state == 0); + d2->state++; + return io_read(d2->buf, sizeof(d2->buf), closer, d2); +} + +static void init_conn(int fd, struct data *d) +{ + ok1(d->state == 0); + d->state++; + io_new_conn(fd, io_write(d->buf, sizeof(d->buf), closer, d)); + io_close_listener(l); +} + +static int make_listen_fd(const char *port, struct addrinfo **info) +{ + int fd, on = 1; + struct addrinfo *addrinfo, hints; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + hints.ai_protocol = 0; + + if (getaddrinfo(NULL, port, &hints, &addrinfo) != 0) + return -1; + + fd = socket(addrinfo->ai_family, addrinfo->ai_socktype, + addrinfo->ai_protocol); + if (fd < 0) + return -1; + + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + if (bind(fd, addrinfo->ai_addr, addrinfo->ai_addrlen) != 0) { + close(fd); + return -1; + } + if (listen(fd, 1) != 0) { + close(fd); + return -1; + } + *info = addrinfo; + return fd; +} + +int main(void) +{ + struct data *d = malloc(sizeof(*d)), *d2 = malloc(sizeof(*d2)); + struct addrinfo *addrinfo; + int fd; + + /* This is how many tests you plan to run */ + plan_tests(8); + d->state = 0; + memset(d->buf, 'a', sizeof(d->buf)); + fd = make_listen_fd(PORT, &addrinfo); + ok1(fd >= 0); + l = io_new_listener(fd, init_conn, d); + ok1(l); + + fd = socket(addrinfo->ai_family, addrinfo->ai_socktype, + addrinfo->ai_protocol); + d2->state = 0; + ok1(io_new_conn(fd, io_connect(fd, addrinfo, connected, d2))); + + ok1(io_loop() == NULL); + ok1(d->state == 2); + ok1(d2->state == 2); + + freeaddrinfo(addrinfo); + free(d); + free(d2); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} -- 2.39.2