From: David van Moolenbroek Date: Fri, 4 Oct 2013 15:57:42 +0000 (+0200) Subject: UDS: support for nonblocking sockets X-Git-Tag: v3.3.0~539 X-Git-Url: http://zhaoyanbai.com/repos/%22../static/icons/zlib_tech.html?a=commitdiff_plain;h=3e8346a8e81713f7608741ea9a1bb9aaa710e03a;p=minix.git UDS: support for nonblocking sockets This patch includes several other fixes, which are now tested in the test56 test set. Change-Id: I9535d5a6c072abf966252838522c5f65b353c6c2 --- diff --git a/drivers/uds/ioc_uds.c b/drivers/uds/ioc_uds.c index fe62a88e0..af1f3d6b7 100644 --- a/drivers/uds/ioc_uds.c +++ b/drivers/uds/ioc_uds.c @@ -125,19 +125,29 @@ do_accept(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant) uds_fd_table[minorparent].child = -1; - /* If the peer is blocked on connect(), revive the peer. */ - if (uds_fd_table[minorpeer].suspended) { + /* If the peer is blocked on connect() or write(), revive the peer. */ + if (uds_fd_table[minorpeer].suspended == UDS_SUSPENDED_CONNECT || + uds_fd_table[minorpeer].suspended == UDS_SUSPENDED_WRITE) { dprintf(("UDS: do_accept(%d): revive %d\n", minor, minorpeer)); uds_unsuspend(minorpeer); } + /* See if we can satisfy an ongoing select. */ + if ((uds_fd_table[minorpeer].sel_ops & CDEV_OP_WR) && + uds_fd_table[minorpeer].size < UDS_BUF) { + /* A write on the peer is possible now. */ + chardriver_reply_select(uds_fd_table[minorpeer].sel_endpt, + minorpeer, CDEV_OP_WR); + uds_fd_table[minorpeer].sel_ops &= ~CDEV_OP_WR; + } + return OK; } static int do_connect(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant) { - int child; + int child, peer; struct sockaddr_un addr; int rc, i, j; @@ -148,9 +158,14 @@ do_connect(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant) uds_fd_table[minor].type != SOCK_SEQPACKET) return EINVAL; - /* The socket must not be connected already. */ - if (uds_fd_table[minor].peer != -1) - return EISCONN; + /* The socket must not be connecting or connected already. */ + peer = uds_fd_table[minor].peer; + if (peer != -1) { + if (uds_fd_table[peer].peer == -1) + return EALREADY; /* connecting */ + else + return EISCONN; /* connected */ + } if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &addr, sizeof(struct sockaddr_un))) != OK) @@ -381,7 +396,7 @@ do_getsockname(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant) static int do_getpeername(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant) { - int rc, peer_minor; + int peer_minor; dprintf(("UDS: do_getpeername(%d)\n", minor)); @@ -479,8 +494,6 @@ do_socketpair(devminor_t minorx, endpoint_t endpt, cp_grant_id_t grant) static int do_getsockopt_sotype(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant) { - int rc; - dprintf(("UDS: do_getsockopt_sotype(%d)\n", minor)); /* If the type hasn't been set yet, we fail the call. */ diff --git a/drivers/uds/uds.c b/drivers/uds/uds.c index 191eb1fd5..f3a79d58d 100644 --- a/drivers/uds/uds.c +++ b/drivers/uds/uds.c @@ -114,10 +114,31 @@ uds_open(devminor_t UNUSED(orig_minor), int access, return CDEV_CLONED | minor; } +static void +uds_reset(devminor_t minor) +{ + /* Disconnect the socket from its peer. */ + uds_fd_table[minor].peer = -1; + + /* Set an error to pass to the caller. */ + uds_fd_table[minor].err = ECONNRESET; + + /* If a process was blocked on I/O, revive it. */ + if (uds_fd_table[minor].suspended != UDS_NOT_SUSPENDED) + uds_unsuspend(minor); + + /* All of the peer's calls will fail immediately now. */ + if (uds_fd_table[minor].sel_ops != 0) { + chardriver_reply_select(uds_fd_table[minor].sel_endpt, minor, + uds_fd_table[minor].sel_ops); + uds_fd_table[minor].sel_ops = 0; + } +} + static int uds_close(devminor_t minor) { - int peer; + int i, peer; dprintf(("UDS: uds_close(%d)\n", minor)); @@ -126,18 +147,26 @@ uds_close(devminor_t minor) if (uds_fd_table[minor].state != UDS_INUSE) return EINVAL; - /* If the socket is connected, disconnect it. */ - if (uds_fd_table[minor].peer != -1) { - peer = uds_fd_table[minor].peer; - - uds_fd_table[peer].peer = -1; - - /* The error to pass to the peer. */ - uds_fd_table[peer].err = ECONNRESET; - - /* If the peer was blocked on I/O, revive it. */ - if (uds_fd_table[peer].suspended != UDS_NOT_SUSPENDED) - uds_unsuspend(peer); + peer = uds_fd_table[minor].peer; + if (peer != -1 && uds_fd_table[peer].peer == -1) { + /* Connecting socket: clear from server's backlog. */ + if (!uds_fd_table[peer].listening) + panic("connecting socket attached to non-server"); + + for (i = 0; i < uds_fd_table[peer].backlog_size; i++) { + if (uds_fd_table[peer].backlog[i] == minor) { + uds_fd_table[peer].backlog[i] = -1; + break; + } + } + } else if (peer != -1) { + /* Connected socket: disconnect it. */ + uds_reset(peer); + } else if (uds_fd_table[minor].listening) { + /* Listening socket: disconnect all sockets in the backlog. */ + for (i = 0; i < uds_fd_table[minor].backlog_size; i++) + if (uds_fd_table[minor].backlog[i] != -1) + uds_reset(uds_fd_table[minor].backlog[i]); } if (uds_fd_table[minor].ancillary_data.nfiledes > 0) @@ -190,7 +219,7 @@ uds_select(devminor_t minor, unsigned int ops, endpoint_t endpt) break; } } - } else if (bytes != SUSPEND) { + } else if (bytes != EDONTREPLY) { ready_ops |= CDEV_OP_RD; /* error */ } } @@ -198,7 +227,7 @@ uds_select(devminor_t minor, unsigned int ops, endpoint_t endpt) /* Check if we can write without blocking. */ if (ops & CDEV_OP_WR) { bytes = uds_perform_write(minor, NONE, GRANT_INVALID, 1, 1); - if (bytes != 0 && bytes != SUSPEND) + if (bytes != 0 && bytes != EDONTREPLY) ready_ops |= CDEV_OP_WR; } @@ -257,7 +286,7 @@ uds_perform_read(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant, return 0; if (pretend) - return SUSPEND; + return EDONTREPLY; if (peer != -1 && uds_fd_table[peer].suspended == UDS_SUSPENDED_WRITE) @@ -335,15 +364,16 @@ uds_perform_write(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant, if (!(uds_fd_table[minor].mode & UDS_W)) return EPIPE; - /* We cannot handle input beyond the buffer size. */ - if (size > UDS_BUF) + /* Datagram messages must fit in the buffer entirely. */ + if (size > UDS_BUF && uds_fd_table[minor].type != SOCK_STREAM) return EMSGSIZE; if (uds_fd_table[minor].type == SOCK_STREAM || uds_fd_table[minor].type == SOCK_SEQPACKET) { /* - * If we're writing to a connection-oriented socket, - * then it needs a peer to write to. + * If we're writing to a connection-oriented socket, then it + * needs a peer to write to. For disconnected sockets, writing + * is an error; for connecting sockets, writes should suspend. */ peer = uds_fd_table[minor].peer; @@ -354,7 +384,8 @@ uds_perform_write(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant, return ECONNRESET; } else return ENOTCONN; - } + } else if (uds_fd_table[peer].peer == -1) /* connecting */ + return EDONTREPLY; } else /* uds_fd_table[minor].type == SOCK_DGRAM */ { peer = -1; @@ -373,12 +404,8 @@ uds_perform_write(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant, } } - if (peer == -1) { - if (pretend) - return SUSPEND; - + if (peer == -1) return ENOENT; - } } /* Check if we write to a closed pipe. */ @@ -401,7 +428,7 @@ uds_perform_write(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant, (uds_fd_table[minor].type == SOCK_SEQPACKET && uds_fd_table[peer].size > 0)) { if (pretend) - return SUSPEND; + return EDONTREPLY; if (uds_fd_table[peer].suspended == UDS_SUSPENDED_READ) panic("reader blocked on full socket"); @@ -532,7 +559,7 @@ static int uds_ioctl(devminor_t minor, unsigned long request, endpoint_t endpt, cp_grant_id_t grant, int flags, endpoint_t user_endpt, cdev_id_t id) { - int rc; + int rc, s; dprintf(("UDS: uds_ioctl(%d, %lu)\n", minor, request)); @@ -550,7 +577,7 @@ uds_ioctl(devminor_t minor, unsigned long request, endpoint_t endpt, /* If the call couldn't complete, suspend the caller. */ if (rc == EDONTREPLY) { /* The suspension type is already set by the IOCTL handler. */ - if (uds_fd_table[minor].suspended == UDS_NOT_SUSPENDED) + if ((s = uds_fd_table[minor].suspended) == UDS_NOT_SUSPENDED) panic("IOCTL did not actually suspend?"); uds_fd_table[minor].susp_endpt = endpt; uds_fd_table[minor].susp_grant = grant; @@ -560,8 +587,10 @@ uds_ioctl(devminor_t minor, unsigned long request, endpoint_t endpt, /* If the call wasn't supposed to block, cancel immediately. */ if (flags & CDEV_NONBLOCK) { uds_cancel(minor, endpt, id); - - rc = EAGAIN; + if (s == UDS_SUSPENDED_CONNECT) + rc = EINPROGRESS; + else + rc = EAGAIN; } } @@ -601,7 +630,8 @@ uds_unsuspend(devminor_t minor) * In both cases, the caller already set up the connection. * The only thing to do here is unblock. */ - r = OK; + r = fdp->err; + fdp->err = 0; break; @@ -618,7 +648,7 @@ static int uds_cancel(devminor_t minor, endpoint_t endpt, cdev_id_t id) { uds_fd_t *fdp; - int i, j; + int i; dprintf(("UDS: uds_cancel(%d)\n", minor)); @@ -641,7 +671,7 @@ uds_cancel(devminor_t minor, endpoint_t endpt, cdev_id_t id) * anymore. */ switch (fdp->suspended) { - case UDS_SUSPENDED_ACCEPT: /* accept() */ + case UDS_SUSPENDED_ACCEPT: /* A partial accept() only sets the server's child. */ for (i = 0; i < NR_FDS; i++) if (uds_fd_table[i].child == minor) @@ -649,26 +679,8 @@ uds_cancel(devminor_t minor, endpoint_t endpt, cdev_id_t id) break; - case UDS_SUSPENDED_CONNECT: /* connect() */ - /* - * A partial connect() sets the address and adds the minor to - * the server backlog. - */ - for (i = 0; i < NR_FDS; i++) { - if (uds_fd_table[i].state != UDS_INUSE) - continue; - - /* Remove the minor from the backlog of any server. */ - for (j = 0; j < uds_fd_table[i].backlog_size; j++) { - if (uds_fd_table[i].backlog[j] == minor) - uds_fd_table[i].backlog[j] = -1; - } - } - - /* Clear the address. */ - memset(&uds_fd_table[minor].addr, '\0', - sizeof(struct sockaddr_un)); - + case UDS_SUSPENDED_CONNECT: + /* Connect requests should continue asynchronously. */ break; case UDS_SUSPENDED_READ: diff --git a/lib/libc/sys-minix/accept.c b/lib/libc/sys-minix/accept.c index bccb81a2a..86e85e9cf 100644 --- a/lib/libc/sys-minix/accept.c +++ b/lib/libc/sys-minix/accept.c @@ -121,6 +121,9 @@ static int _uds_accept(int sock, struct sockaddr *__restrict address, if (s1 == -1) return s1; + /* Copy file descriptor flags from the listening socket. */ + fcntl(s1, F_SETFL, fcntl(sock, F_GETFL)); + r= ioctl(s1, NWIOSUDSACCEPT, address); if (r == -1) { int ioctl_errno = errno; diff --git a/test/test56.c b/test/test56.c index 05222723c..fb13872c4 100644 --- a/test/test56.c +++ b/test/test56.c @@ -2783,10 +2783,8 @@ void test_select() int res = 0; char buf[1]; - for (i = 0; i < OPEN_MAX; i++) { - FD_CLR(i, &readfds); - FD_CLR(i, &writefds); - } + FD_ZERO(&readfds); + FD_ZERO(&writefds); tv.tv_sec = 2; tv.tv_usec = 0; /* 2 sec time out */ @@ -2795,7 +2793,7 @@ void test_select() test_fail("Can't open socket pair."); } FD_SET(socks[0], &readfds); - nfds = socks[1] + 1; + nfds = socks[0] + 1; /* Close the write end of the socket to generate EOF on read end */ if ((res = shutdown(socks[1], SHUT_WR)) != 0) { @@ -2853,7 +2851,47 @@ void test_select() } close(socks[1]); +} + +void test_select_close(void) +{ + int res, socks[2]; + fd_set readfds; + struct timeval tv; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) < 0) { + test_fail("Can't open socket pair."); + } + + switch (fork()) { + case 0: + sleep(1); + + exit(0); + case -1: + test_fail("Can't fork."); + default: + break; + } + + close(socks[1]); + + FD_ZERO(&readfds); + FD_SET(socks[0], &readfds); + tv.tv_sec = 2; + tv.tv_usec = 0; /* 2 sec time out */ + + res = select(socks[0] + 1, &readfds, NULL, NULL, &tv); + if (res != 1) { + test_fail("select should've returned 1 ready fd\n"); + } + if (!(FD_ISSET(socks[0], &readfds))) { + test_fail("The server didn't respond within 2 seconds"); + } + wait(NULL); + + close(socks[0]); } void test_fchmod() @@ -2889,6 +2927,562 @@ void test_fchmod() close(socks[1]); } +static void +check_select(int sd, int rd, int wr, int block) +{ + fd_set read_set, write_set; + struct timeval tv; + + FD_ZERO(&read_set); + if (rd != -1) + FD_SET(sd, &read_set); + + FD_ZERO(&write_set); + if (wr != -1) + FD_SET(sd, &write_set); + + tv.tv_sec = block ? 2 : 0; + tv.tv_usec = 0; + + if (select(sd + 1, &read_set, &write_set, NULL, &tv) < 0) + test_fail("select() failed unexpectedly"); + + if (rd != -1 && !!FD_ISSET(sd, &read_set) != rd) + test_fail("select() mismatch on read operation"); + + if (wr != -1 && !!FD_ISSET(sd, &write_set) != wr) + test_fail("select() mismatch on write operation"); +} + +/* + * Verify that: + * - a nonblocking connecting socket for which there is no accepter, will + * return EINPROGRESS and complete in the background later; + * - a nonblocking listening socket will return EAGAIN on accept; + * - connecting a connecting socket yields EALREADY; + * - connecting a connected socket yields EISCONN; + * - selecting for read and write on a connecting socket will only satisfy the + * write only once it is connected; + * - doing a nonblocking write on a connecting socket yields EAGAIN; + * - doing a nonblocking read on a connected socket with no pending data yields + * EAGAIN. + */ +static void +test_nonblock(void) +{ + char buf[BUFSIZE]; + socklen_t len; + int server_sd, client_sd; + struct sockaddr_un server_addr, client_addr, addr; + int status; + + memset(buf, 0, sizeof(buf)); + + memset(&server_addr, 0, sizeof(server_addr)); + strlcpy(server_addr.sun_path, TEST_SUN_PATH, + sizeof(server_addr.sun_path)); + server_addr.sun_family = AF_UNIX; + + client_addr = server_addr; + + SOCKET(server_sd, PF_UNIX, SOCK_STREAM, 0); + + if (bind(server_sd, (struct sockaddr *) &server_addr, + sizeof(struct sockaddr_un)) == -1) + test_fail("bind() should have worked"); + + if (listen(server_sd, 8) == -1) + test_fail("listen() should have worked"); + + fcntl(server_sd, F_SETFL, fcntl(server_sd, F_GETFL) | O_NONBLOCK); + + check_select(server_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); + + len = sizeof(addr); + if (accept(server_sd, (struct sockaddr *) &addr, &len) != -1 || + errno != EAGAIN) + test_fail("accept() should have yielded EAGAIN"); + + SOCKET(client_sd, PF_UNIX, SOCK_STREAM, 0); + + fcntl(client_sd, F_SETFL, fcntl(client_sd, F_GETFL) | O_NONBLOCK); + + if (connect(client_sd, (struct sockaddr *) &client_addr, + sizeof(struct sockaddr_un)) != -1 || errno != EINPROGRESS) + test_fail("connect() should have yielded EINPROGRESS"); + + check_select(client_sd, 0 /*read*/, 0 /*write*/, 0 /*block*/); + + if (connect(client_sd, (struct sockaddr *) &client_addr, + sizeof(struct sockaddr_un)) != -1 || errno != EALREADY) + test_fail("connect() should have yielded EALREADY"); + + if (recv(client_sd, buf, sizeof(buf), 0) != -1 || errno != EAGAIN) + test_fail("recv() should have yielded EAGAIN"); + + /* This may be an implementation aspect, or even plain wrong (?). */ + if (send(client_sd, buf, sizeof(buf), 0) != -1 || errno != EAGAIN) + test_fail("send() should have yielded EAGAIN"); + + switch (fork()) { + case 0: + close(client_sd); + + check_select(server_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/); + + len = sizeof(addr); + client_sd = accept(server_sd, (struct sockaddr *) &addr, &len); + if (client_sd == -1) + test_fail("accept() should have succeeded"); + + check_select(server_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); + + close(server_sd); + + if (write(client_sd, buf, 1) != 1) + test_fail("write() should have succeeded"); + + /* Wait for the client side to close. */ + check_select(client_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); + check_select(client_sd, 1 /*read*/, -1 /*write*/, 1 /*block*/); + check_select(client_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/); + + exit(errct); + case -1: + test_fail("can't fork"); + default: + break; + } + + close(server_sd); + + check_select(client_sd, 0 /*read*/, 1 /*write*/, 1 /*block*/); + check_select(client_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); + + if (connect(client_sd, (struct sockaddr *) &client_addr, + sizeof(struct sockaddr_un)) != -1 || errno != EISCONN) + test_fail("connect() should have yielded EISCONN"); + + check_select(client_sd, 1 /*read*/, -1 /*write*/, 1 /*block*/); + check_select(client_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/); + + if (read(client_sd, buf, 1) != 1) + test_fail("read() should have succeeded"); + + check_select(client_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); + + if (read(client_sd, buf, 1) != -1 || errno != EAGAIN) + test_fail("read() should have yielded EAGAIN"); + + /* Let the child process block on the select waiting for the close. */ + sleep(1); + + close(client_sd); + + if (wait(&status) <= 0) + test_fail("wait() should have succeeded"); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + test_fail("child process failed the test"); + + UNLINK(TEST_SUN_PATH); +} + +/* + * Verify that a nonblocking connect for which there is an accepter, succeeds + * immediately. A pretty lame test, only here for completeness. + */ +static void +test_connect_nb(void) +{ + socklen_t len; + int server_sd, client_sd; + struct sockaddr_un server_addr, client_addr, addr; + int status; + + memset(&server_addr, 0, sizeof(server_addr)); + strlcpy(server_addr.sun_path, TEST_SUN_PATH, + sizeof(server_addr.sun_path)); + server_addr.sun_family = AF_UNIX; + + client_addr = server_addr; + + SOCKET(server_sd, PF_UNIX, SOCK_STREAM, 0); + + if (bind(server_sd, (struct sockaddr *) &server_addr, + sizeof(struct sockaddr_un)) == -1) + test_fail("bind() should have worked"); + + if (listen(server_sd, 8) == -1) + test_fail("listen() should have worked"); + + switch (fork()) { + case 0: + len = sizeof(addr); + if (accept(server_sd, (struct sockaddr *) &addr, &len) == -1) + test_fail("accept() should have succeeded"); + + exit(errct); + case -1: + test_fail("can't fork"); + default: + break; + } + + close(server_sd); + + sleep(1); + + SOCKET(client_sd, PF_UNIX, SOCK_STREAM, 0); + + fcntl(client_sd, F_SETFL, fcntl(client_sd, F_GETFL) | O_NONBLOCK); + + if (connect(client_sd, (struct sockaddr *) &client_addr, + sizeof(struct sockaddr_un)) != 0) + test_fail("connect() should have succeeded"); + + close(client_sd); + + if (wait(&status) <= 0) + test_fail("wait() should have succeeded"); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + test_fail("child process failed the test"); + + UNLINK(TEST_SUN_PATH); +} + +static void +dummy_handler(int sig) +{ + /* Nothing. */ +} + +/* + * Verify that: + * - interrupting a blocking connect will return EINTR but complete in the + * background later; + * - doing a blocking write on an asynchronously connecting socket succeeds + * once the socket is connected. + * - doing a nonblocking write on a connected socket with lots of pending data + * yields EAGAIN. + */ +static void +test_intr(void) +{ + struct sigaction act, oact; + char buf[BUFSIZE]; + socklen_t len; + int server_sd, client_sd; + struct sockaddr_un server_addr, client_addr, addr; + int r, status; + + memset(buf, 0, sizeof(buf)); + + memset(&server_addr, 0, sizeof(server_addr)); + strlcpy(server_addr.sun_path, TEST_SUN_PATH, + sizeof(server_addr.sun_path)); + server_addr.sun_family = AF_UNIX; + + client_addr = server_addr; + + SOCKET(server_sd, PF_UNIX, SOCK_STREAM, 0); + + if (bind(server_sd, (struct sockaddr *) &server_addr, + sizeof(struct sockaddr_un)) == -1) + test_fail("bind() should have worked"); + + if (listen(server_sd, 8) == -1) + test_fail("listen() should have worked"); + + SOCKET(client_sd, PF_UNIX, SOCK_STREAM, 0); + + memset(&act, 0, sizeof(act)); + act.sa_handler = dummy_handler; + if (sigaction(SIGALRM, &act, &oact) == -1) + test_fail("sigaction() should have succeeded"); + + alarm(1); + + if (connect(client_sd, (struct sockaddr *) &client_addr, + sizeof(struct sockaddr_un)) != -1 || errno != EINTR) + test_fail("connect() should have yielded EINTR"); + + check_select(client_sd, 0 /*read*/, 0 /*write*/, 0 /*block*/); + + switch (fork()) { + case 0: + close(client_sd); + + check_select(server_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/); + + len = sizeof(addr); + client_sd = accept(server_sd, (struct sockaddr *) &addr, &len); + if (client_sd == -1) + test_fail("accept() should have succeeded"); + + check_select(server_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); + + close(server_sd); + + check_select(client_sd, 1 /*read*/, -1 /*write*/, 1 /*block*/); + check_select(client_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/); + + if (recv(client_sd, buf, sizeof(buf), 0) != sizeof(buf)) + test_fail("recv() should have yielded bytes"); + + /* No partial transfers should be happening. */ + check_select(client_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); + + fcntl(client_sd, F_SETFL, fcntl(client_sd, F_GETFL) | + O_NONBLOCK); + + /* We can only test nonblocking writes by filling the pipe. */ + while ((r = write(client_sd, buf, sizeof(buf))) > 0); + + if (r != -1 || errno != EAGAIN) + test_fail("write() should have yielded EAGAIN"); + + check_select(client_sd, 0 /*read*/, 0 /*write*/, 0 /*block*/); + + if (write(client_sd, buf, 1) != -1 || errno != EAGAIN) + test_fail("write() should have yielded EAGAIN"); + + exit(errct); + case -1: + test_fail("can't fork"); + default: + break; + } + + close(server_sd); + + if (send(client_sd, buf, sizeof(buf), 0) != sizeof(buf)) + test_fail("send() should have succeded"); + + check_select(client_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); + + if (wait(&status) <= 0) + test_fail("wait() should have succeeded"); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + test_fail("child process failed the test"); + + check_select(client_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/); + + close(client_sd); + + sigaction(SIGALRM, &oact, NULL); + + UNLINK(TEST_SUN_PATH); +} + +/* + * Verify that closing a connecting socket before it is accepted will result in + * no activity on the accepting side later. + */ +static void +test_connect_close(void) +{ + int server_sd, client_sd; + struct sockaddr_un server_addr, client_addr; + socklen_t len; + + memset(&server_addr, 0, sizeof(server_addr)); + strlcpy(server_addr.sun_path, TEST_SUN_PATH, + sizeof(server_addr.sun_path)); + server_addr.sun_family = AF_UNIX; + + client_addr = server_addr; + + SOCKET(server_sd, PF_UNIX, SOCK_STREAM, 0); + + if (bind(server_sd, (struct sockaddr *) &server_addr, + sizeof(struct sockaddr_un)) == -1) + test_fail("bind() should have worked"); + + if (listen(server_sd, 8) == -1) + test_fail("listen() should have worked"); + + fcntl(server_sd, F_SETFL, fcntl(server_sd, F_GETFL) | O_NONBLOCK); + + check_select(server_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); + + SOCKET(client_sd, PF_UNIX, SOCK_STREAM, 0); + + fcntl(client_sd, F_SETFL, fcntl(client_sd, F_GETFL) | O_NONBLOCK); + + if (connect(client_sd, (struct sockaddr *) &client_addr, + sizeof(struct sockaddr_un)) != -1 || errno != EINPROGRESS) + test_fail("connect() should have yielded EINPROGRESS"); + + check_select(client_sd, 0 /*read*/, 0 /*write*/, 0 /*block*/); + check_select(server_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/); + + close(client_sd); + + check_select(server_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/); + + len = sizeof(client_addr); + if (accept(server_sd, (struct sockaddr *) &client_addr, &len) != -1 || + errno != EAGAIN) + test_fail("accept() should have yielded EAGAIN"); + + close(server_sd); + + UNLINK(TEST_SUN_PATH); +} + +/* + * Verify that closing a listening socket will cause a blocking connect to fail + * with ECONNRESET, and that a subsequent write will yield EPIPE. + */ +static void +test_listen_close(void) +{ + socklen_t len; + int server_sd, client_sd; + struct sockaddr_un server_addr, client_addr, addr; + int status; + char byte; + + memset(&server_addr, 0, sizeof(server_addr)); + strlcpy(server_addr.sun_path, TEST_SUN_PATH, + sizeof(server_addr.sun_path)); + server_addr.sun_family = AF_UNIX; + + client_addr = server_addr; + + SOCKET(server_sd, PF_UNIX, SOCK_STREAM, 0); + + if (bind(server_sd, (struct sockaddr *) &server_addr, + sizeof(struct sockaddr_un)) == -1) + test_fail("bind() should have worked"); + + if (listen(server_sd, 8) == -1) + test_fail("listen() should have worked"); + + switch (fork()) { + case 0: + sleep(1); + + exit(0); + case -1: + test_fail("can't fork"); + default: + break; + } + + close(server_sd); + + SOCKET(client_sd, PF_UNIX, SOCK_STREAM, 0); + + byte = 0; + if (write(client_sd, &byte, 1) != -1 || errno != ENOTCONN) + /* Yes, you fucked up the fix for the FIXME below. */ + test_fail("write() should have yielded ENOTCONN"); + + if (connect(client_sd, (struct sockaddr *) &client_addr, + sizeof(struct sockaddr_un)) != -1 || errno != ECONNRESET) + test_fail("connect() should have yielded ECONNRESET"); + + /* + * FIXME: currently UDS cannot distinguish between sockets that have + * not yet been connected, and sockets that have been disconnected. + * Thus, we get the same error for both: ENOTCONN instead of EPIPE. + */ +#if 0 + if (write(client_sd, &byte, 1) != -1 || errno != EPIPE) + test_fail("write() should have yielded EPIPE"); +#endif + + close(client_sd); + + if (wait(&status) <= 0) + test_fail("wait() should have succeeded"); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + test_fail("child process failed the test"); + + UNLINK(TEST_SUN_PATH); +} + +/* + * Verify that closing a listening socket will cause a nonblocking connect to + * result in the socket becoming readable and writable, and yielding ECONNRESET + * and EPIPE on the next two writes, respectively. + */ +static void +test_listen_close_nb(void) +{ + socklen_t len; + int server_sd, client_sd; + struct sockaddr_un server_addr, client_addr, addr; + int status; + char byte; + + memset(&server_addr, 0, sizeof(server_addr)); + strlcpy(server_addr.sun_path, TEST_SUN_PATH, + sizeof(server_addr.sun_path)); + server_addr.sun_family = AF_UNIX; + + client_addr = server_addr; + + SOCKET(server_sd, PF_UNIX, SOCK_STREAM, 0); + + if (bind(server_sd, (struct sockaddr *) &server_addr, + sizeof(struct sockaddr_un)) == -1) + test_fail("bind() should have worked"); + + if (listen(server_sd, 8) == -1) + test_fail("listen() should have worked"); + + switch (fork()) { + case 0: + sleep(1); + + exit(0); + case -1: + test_fail("can't fork"); + default: + break; + } + + close(server_sd); + + SOCKET(client_sd, PF_UNIX, SOCK_STREAM, 0); + + fcntl(client_sd, F_SETFL, fcntl(client_sd, F_GETFL) | O_NONBLOCK); + + if (connect(client_sd, (struct sockaddr *) &client_addr, + sizeof(struct sockaddr_un)) != -1 || errno != EINPROGRESS) + test_fail("connect() should have yielded EINPROGRESS"); + + check_select(client_sd, 0 /*read*/, 0 /*write*/, 0 /*block*/); + check_select(client_sd, 1 /*read*/, 1 /*write*/, 1 /*block*/); + + byte = 0; + if (write(client_sd, &byte, 1) != -1 || errno != ECONNRESET) + test_fail("write() should have yielded ECONNRESET"); + + /* + * FIXME: currently UDS cannot distinguish between sockets that have + * not yet been connected, and sockets that have been disconnected. + * Thus, we get the same error for both: ENOTCONN instead of EPIPE. + */ +#if 0 + if (write(client_sd, &byte, 1) != -1 || errno != EPIPE) + test_fail("write() should have yielded EPIPE"); +#endif + + check_select(client_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/); + + close(client_sd); + + if (wait(&status) <= 0) + test_fail("wait() should have succeeded"); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + test_fail("child process failed the test"); + + UNLINK(TEST_SUN_PATH); +} + int main(int argc, char *argv[]) { int i; @@ -2929,9 +3523,16 @@ int main(int argc, char *argv[]) test_scm_credentials(); test_fd_passing(); test_select(); + test_select_close(); test_fchmod(); + test_nonblock(); + test_connect_nb(); + test_intr(); + test_connect_close(); + test_listen_close(); + test_listen_close_nb(); + quit(); return -1; /* we should never get here */ } -