Skip to content

Commit 6024e11

Browse files
authored
wasip3: Fix trapping on read/write after shutdown (#790)
On shutdown the streams are closed so the `__wasilibc_{read,write}` functions would end up trapping when passing a zero handle to the runtime. This commit fixes this by adding checks to these function and deferring to EOF handling in such a situation. Additionally the write EOF handler for TCP is updated to return -1 and set errno to `EPIPE` rather than returning 0.
1 parent 52bbf12 commit 6024e11

4 files changed

Lines changed: 83 additions & 1 deletion

File tree

libc-bottom-half/sources/file_utils.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,11 @@ ssize_t __wasilibc_write(wasi_write_t *write, const void *buffer,
351351
#elif defined(__wasip3__)
352352
wasip3_io_state_t *state = write->state;
353353

354+
// If this stream is closed, for example with a TCP shutdown, then it's
355+
// closed and we're at EOF.
356+
if (state->stream == 0)
357+
return write->eof(write->eof_data);
358+
354359
// First resolve any pending I/O, should it exist.
355360
if (wasip3_write_resolve_pending(write) < 0)
356361
return -1;
@@ -550,6 +555,11 @@ ssize_t __wasilibc_read(wasi_read_t *read, void *buffer, size_t length) {
550555
wasip3_io_state_t *state = read->state;
551556
wasip3_event_t event;
552557

558+
// If this stream is closed, for example with a TCP shutdown, then it's
559+
// closed and we're at EOF.
560+
if (state->stream == 0)
561+
return read->eof(read->eof_data);
562+
553563
// If there's active I/O in progress for this stream then this must wait for
554564
// it to complete.
555565
if (state->flags & WASIP3_IO_INPROGRESS) {
@@ -669,6 +679,13 @@ static void wasip3_poll_write_ready(void *data, poll_state_t *state,
669679

670680
static int wasip3_stream_poll(wasip3_io_state_t *iostate, poll_state_t *state,
671681
short events, poll_ready_t ready) {
682+
// If the stream is closed then it's immediately ready for reading/writing as
683+
// that'll resolve with an error/0/etc.
684+
if (iostate->stream == 0) {
685+
__wasilibc_poll_ready(state, events);
686+
return 0;
687+
}
688+
672689
// If the I/O stream is finished it'll never block so it's always ready.
673690
if (iostate->flags & WASIP3_IO_DONE) {
674691
__wasilibc_poll_ready(state, events);

libc-bottom-half/sources/tcp.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,8 @@ static int tcp_write_eof(void *data) {
220220
if (result.is_err)
221221
return __wasilibc_socket_error_to_errno(&result.val.err);
222222
}
223-
return 0;
223+
errno = EPIPE;
224+
return -1;
224225
}
225226
#endif
226227

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ if (NOT (WASI STREQUAL "p1"))
417417
add_wasilibc_test(sockets-nonblocking-udp-no-connection.c NETWORK)
418418
add_wasilibc_test(sockets-eof-delayed.c NETWORK)
419419
add_wasilibc_test(sockets-nonblocking-accept-multiple.c NETWORK)
420+
add_wasilibc_test(sockets-nonblocking-shutdown.c NETWORK)
420421

421422
# Define executables for server/client tests, and they're paired together in
422423
# various combinations below for various tests.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#include "test.h"
2+
#include <arpa/inet.h>
3+
#include <errno.h>
4+
#include <fcntl.h>
5+
#include <netdb.h>
6+
#include <netinet/in.h>
7+
#include <poll.h>
8+
#include <stdio.h>
9+
#include <stdlib.h>
10+
#include <string.h>
11+
#include <sys/socket.h>
12+
#include <unistd.h>
13+
14+
#define TEST(c) \
15+
do { \
16+
errno = 0; \
17+
if (!(c)) \
18+
t_error("%s failed (errno = %d)\n", #c, errno); \
19+
} while (0)
20+
21+
#define N 10
22+
23+
int main() {
24+
int listener_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
25+
26+
// Setup a listener bound to port 0 to have the OS assign us one.
27+
struct sockaddr_in server_address;
28+
socklen_t server_address_len = sizeof(server_address);
29+
server_address.sin_family = AF_INET;
30+
server_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
31+
server_address.sin_port = 0;
32+
TEST(bind(listener_fd, (struct sockaddr *)&server_address,
33+
sizeof(server_address)) != -1);
34+
TEST(getsockname(listener_fd, (struct sockaddr *)&server_address,
35+
&server_address_len) != -1);
36+
TEST(listen(listener_fd, N) != -1);
37+
38+
int client_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
39+
TEST(client_fd != -1);
40+
int rc;
41+
TEST((rc = connect(client_fd, (struct sockaddr *)&server_address,
42+
server_address_len)) != -1 ||
43+
errno == EINPROGRESS);
44+
if (rc == -1) {
45+
struct pollfd pfd = {.fd = client_fd, .events = POLLWRNORM};
46+
TEST(poll(&pfd, 1, -1) != -1);
47+
}
48+
49+
TEST(shutdown(client_fd, SHUT_RDWR) != -1);
50+
51+
char buf[10];
52+
TEST(recv(client_fd, buf, sizeof(buf), 0) == 0);
53+
TEST(send(client_fd, buf, sizeof(buf), 0) == -1 && errno == EPIPE);
54+
55+
struct pollfd pfd = {.fd = client_fd, .events = POLLWRNORM};
56+
TEST(poll(&pfd, 1, -1) == 1);
57+
TEST(pfd.revents & POLLWRNORM);
58+
pfd.events = POLLRDNORM;
59+
TEST(poll(&pfd, 1, -1) == 1);
60+
TEST(pfd.revents & POLLRDNORM);
61+
62+
return t_status;
63+
}

0 commit comments

Comments
 (0)