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:
parent
1e07186caf
commit
3e8346a8e8
4 changed files with 697 additions and 68 deletions
|
@ -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. */
|
||||
|
|
|
@ -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;
|
||||
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,13 +404,9 @@ 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. */
|
||||
if (!(uds_fd_table[peer].mode & UDS_R))
|
||||
|
@ -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,7 +587,9 @@ 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);
|
||||
|
||||
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:
|
||||
|
|
|
@ -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;
|
||||
|
|
613
test/test56.c
613
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 */
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue