From: Rusty Russell Date: Mon, 14 Oct 2013 11:03:07 +0000 (+1030) Subject: ccan/io: save errno on io_close, for finish functions. X-Git-Url: http://git.ozlabs.org/?p=ccan;a=commitdiff_plain;h=7bfb7a1cf4f7c5eaf4081ef7128b7fc7d1cf926e;hp=306b6b0e89957bc1f2faa0a9b30a8398d1f40dfc ccan/io: save errno on io_close, for finish functions. Signed-off-by: Rusty Russell --- diff --git a/ccan/io/io.c b/ccan/io/io.c index 352afe33..b6ae56ac 100644 --- a/ccan/io/io.c +++ b/ccan/io/io.c @@ -325,6 +325,7 @@ struct io_plan io_close(void) plan.pollflag = 0; /* This means we're closing. */ plan.next = NULL; + plan.u.close.saved_errno = errno; io_plan_debug(&plan); return plan; diff --git a/ccan/io/io.h b/ccan/io/io.h index 8199bda9..6553e7ab 100644 --- a/ccan/io/io.h +++ b/ccan/io/io.h @@ -38,6 +38,9 @@ struct io_plan { const char *buf; size_t *lenp; } writepart; + struct { + int saved_errno; + } close; struct { void *p; size_t len; @@ -84,7 +87,8 @@ struct io_conn *io_new_conn_(int fd, struct io_plan plan); * @arg: the argument to @finish. * * @finish will be called when an I/O operation fails, or you call - * io_close() on the connection. + * io_close() on the connection. errno will be set to the value + * after the failed I/O, or at the call to io_close(). */ #define io_set_finish(conn, finish, arg) \ io_set_finish_((conn), \ diff --git a/ccan/io/poll.c b/ccan/io/poll.c index 42ed67ad..3ed6321b 100644 --- a/ccan/io/poll.c +++ b/ccan/io/poll.c @@ -7,6 +7,7 @@ #include #include #include +#include static size_t num_fds = 0, max_fds = 0, num_closing = 0, num_waiting = 0; static struct pollfd *pollfds = NULL; @@ -173,8 +174,10 @@ bool add_duplex(struct io_conn *c) static void del_conn(struct io_conn *conn) { - if (conn->finish) + if (conn->finish) { + errno = conn->plan.u.close.saved_errno; conn->finish(conn, conn->finish_arg); + } if (timeout_active(conn)) backend_del_timeout(conn); free(conn->timeout); @@ -341,6 +344,7 @@ void *io_loop(void) } else if (events & (POLLHUP|POLLNVAL|POLLERR)) { r--; set_current(c); + errno = EBADF; set_plan(c, io_close()); if (c->duplex) { set_current(c->duplex); diff --git a/ccan/io/test/run-18-errno-DEBUG.c b/ccan/io/test/run-18-errno-DEBUG.c new file mode 100644 index 00000000..bfa66157 --- /dev/null +++ b/ccan/io/test/run-18-errno-DEBUG.c @@ -0,0 +1,8 @@ +#define DEBUG +#define PORT "64018" +#define main real_main +int real_main(void); +#include "run-18-errno.c" +#undef main +static bool always_debug(struct io_conn *conn) { return true; } +int main(void) { io_debug = always_debug; return real_main(); } diff --git a/ccan/io/test/run-18-errno.c b/ccan/io/test/run-18-errno.c new file mode 100644 index 00000000..985a3229 --- /dev/null +++ b/ccan/io/test/run-18-errno.c @@ -0,0 +1,120 @@ +#include +/* Include the C files directly. */ +#include +#include +#include +#include +#include + +#ifndef PORT +#define PORT "65018" +#endif + +static void finish_100(struct io_conn *conn, int *state) +{ + ok1(errno == 100); + ok1(*state == 1); + (*state)++; +} + +static void finish_EBADF(struct io_conn *conn, int *state) +{ + ok1(errno == EBADF); + ok1(*state == 3); + (*state)++; + io_break(state + 1, io_close()); +} + +static void init_conn(int fd, int *state) +{ + if (*state == 0) { + (*state)++; + errno = 100; + io_set_finish(io_new_conn(fd, io_close()), finish_100, state); + } else { + ok1(*state == 2); + (*state)++; + close(fd); + errno = 0; + io_set_finish(io_new_conn(fd, io_read(state, 0, + io_close_cb, NULL)), + finish_EBADF, state); + } +} + +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) +{ + int state = 0; + struct addrinfo *addrinfo; + struct io_listener *l; + int fd; + + /* This is how many tests you plan to run */ + plan_tests(12); + fd = make_listen_fd(PORT, &addrinfo); + ok1(fd >= 0); + l = io_new_listener(fd, init_conn, &state); + ok1(l); + fflush(stdout); + if (!fork()) { + io_close_listener(l); + fd = socket(addrinfo->ai_family, addrinfo->ai_socktype, + addrinfo->ai_protocol); + if (fd < 0) + exit(1); + if (connect(fd, addrinfo->ai_addr, addrinfo->ai_addrlen) != 0) + exit(2); + close(fd); + fd = socket(addrinfo->ai_family, addrinfo->ai_socktype, + addrinfo->ai_protocol); + if (fd < 0) + exit(3); + if (connect(fd, addrinfo->ai_addr, addrinfo->ai_addrlen) != 0) + exit(4); + close(fd); + freeaddrinfo(addrinfo); + exit(0); + } + freeaddrinfo(addrinfo); + ok1(io_loop() == &state + 1); + ok1(state == 4); + io_close_listener(l); + ok1(wait(&state)); + ok1(WIFEXITED(state)); + ok1(WEXITSTATUS(state) == 0); + + /* This exits depending on whether all tests passed */ + return exit_status(); +}