UDS: support for nonblocking sockets

This patch includes several other fixes, which are now tested in the
test56 test set.

Change-Id: I9535d5a6c072abf966252838522c5f65b353c6c2
This commit is contained in:
David van Moolenbroek 2013-10-04 17:57:42 +02:00 committed by Lionel Sambuc
parent 1e07186caf
commit 3e8346a8e8
4 changed files with 697 additions and 68 deletions

View 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. */

View 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;
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");
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);
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:

View 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;

View 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 */
}