]> Zhao Yanbai Git Server - minix.git/commitdiff
UDS: support for nonblocking sockets
authorDavid van Moolenbroek <david@minix3.org>
Fri, 4 Oct 2013 15:57:42 +0000 (17:57 +0200)
committerLionel Sambuc <lionel@minix3.org>
Sat, 1 Mar 2014 08:04:57 +0000 (09:04 +0100)
This patch includes several other fixes, which are now tested in the
test56 test set.

Change-Id: I9535d5a6c072abf966252838522c5f65b353c6c2

drivers/uds/ioc_uds.c
drivers/uds/uds.c
lib/libc/sys-minix/accept.c
test/test56.c

index fe62a88e01830e66106397d3c6ac403da9070dc2..af1f3d6b7a08afb2e412bb07835556c4001ea75e 100644 (file)
@@ -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. */
index 191eb1fd567423af54bb395f0abb2ff053b4749d..f3a79d58d42cb877ea1403739fa969343bc52252 100644 (file)
@@ -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:
index bccb81a2a4985cf5c044a231530d6b9eb2d840f2..86e85e9cfb2325dec5b1793e91a6ad4765f24e49 100644 (file)
@@ -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;
index 05222723cacafdf1ac229636bff7bc91a7eedf90..fb13872c4c4af52e9bb62da9d3f29ce1c71dd6a9 100644 (file)
@@ -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 */
 }
-