Browse Source

ccan/io: save errno on io_close, for finish functions.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Rusty Russell 12 years ago
parent
commit
7bfb7a1cf4
5 changed files with 139 additions and 2 deletions
  1. 1 0
      ccan/io/io.c
  2. 5 1
      ccan/io/io.h
  3. 5 1
      ccan/io/poll.c
  4. 8 0
      ccan/io/test/run-18-errno-DEBUG.c
  5. 120 0
      ccan/io/test/run-18-errno.c

+ 1 - 0
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;

+ 5 - 1
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),						\

+ 5 - 1
ccan/io/poll.c

@@ -7,6 +7,7 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <limits.h>
+#include <errno.h>
 
 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);

+ 8 - 0
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(); }

+ 120 - 0
ccan/io/test/run-18-errno.c

@@ -0,0 +1,120 @@
+#include <ccan/io/io.h>
+/* Include the C files directly. */
+#include <ccan/io/poll.c>
+#include <ccan/io/io.c>
+#include <ccan/tap/tap.h>
+#include <sys/wait.h>
+#include <stdio.h>
+
+#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();
+}