diff --git a/lib/libc/sys/poll.2 b/lib/libc/sys/poll.2 --- a/lib/libc/sys/poll.2 +++ b/lib/libc/sys/poll.2 @@ -126,6 +126,15 @@ should never be present in the .Fa revents bitmask at the same time. +.It POLLRDHUP +Remote peer closed connection, or shut down writing. +Unlike +POLLHUP, +POLLRDHUP +must be present in the +.Fa events +bitmask to be reported. +Applies only to stream sockets. .It POLLNVAL The file descriptor is not open, or in capability mode the file descriptor has insufficient rights. @@ -261,6 +270,9 @@ The .Fn ppoll is not specified by POSIX. +The +POLLRDHUP +flag is not specified by POSIX, but is compatible with Linux and illumos. .Sh HISTORY The .Fn poll diff --git a/sys/kern/uipc_socket.c b/sys/kern/uipc_socket.c --- a/sys/kern/uipc_socket.c +++ b/sys/kern/uipc_socket.c @@ -3567,9 +3567,11 @@ revents |= POLLHUP; } } + if (so->so_rcv.sb_state & SBS_CANTRCVMORE) + revents |= events & POLLRDHUP; if (revents == 0) { if (events & - (POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND)) { + (POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND | POLLRDHUP)) { selrecord(td, &so->so_rdsel); so->so_rcv.sb_flags |= SB_SEL; } diff --git a/sys/sys/poll.h b/sys/sys/poll.h --- a/sys/sys/poll.h +++ b/sys/sys/poll.h @@ -71,6 +71,7 @@ #if __BSD_VISIBLE /* General FreeBSD extension (currently only supported for sockets): */ #define POLLINIGNEOF 0x2000 /* like POLLIN, except ignore EOF */ +#define POLLRDHUP 0x4000 /* half shut down */ #endif /* diff --git a/tests/sys/netinet/socket_afinet.c b/tests/sys/netinet/socket_afinet.c --- a/tests/sys/netinet/socket_afinet.c +++ b/tests/sys/netinet/socket_afinet.c @@ -31,6 +31,7 @@ #include #include #include +#include #include @@ -89,12 +90,152 @@ close(sd); } +ATF_TC_WITHOUT_HEAD(socket_afinet_poll_no_rdhup); +ATF_TC_BODY(socket_afinet_poll_no_rdhup, tc) +{ + int ss, ss2, cs, rc; + struct sockaddr_in sin; + struct pollfd pfd; + int one = 1; + + /* Verify that we don't expose POLLRDHUP if not requested. */ + + /* Server setup. */ + ss = socket(PF_INET, SOCK_STREAM, 0); + ATF_CHECK(ss >= 0); + rc = setsockopt(ss, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); + ATF_CHECK_EQ(0, rc); + bzero(&sin, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_len = sizeof(sin); + sin.sin_port = htons(6666); + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + rc = bind(ss, (struct sockaddr *)&sin, sizeof(sin)); + ATF_CHECK_EQ(0, rc); + rc = listen(ss, 1); + ATF_CHECK_EQ(0, rc); + + /* Client connects, server accepts. */ + cs = socket(PF_INET, SOCK_STREAM, 0); + ATF_CHECK(cs >= 0); + rc = connect(cs, (struct sockaddr *)&sin, sizeof(sin)); + ATF_CHECK_EQ(0, rc); + ss2 = accept(ss, NULL, NULL); + ATF_CHECK(ss2 >= 0); + + /* Server can write, sees only POLLOUT. */ + pfd.fd = ss2; + pfd.events = POLLIN | POLLOUT; + rc = poll(&pfd, 1, 0); + ATF_CHECK_EQ(1, rc); + ATF_CHECK_EQ(POLLOUT, pfd.revents); + + /* Client closes socket! */ + rc = close(cs); + ATF_CHECK_EQ(0, rc); + + /* + * Server now sees POLLIN, but not POLLRDHUP because we didn't ask. + * Need non-zero timeout to wait for the FIN to arrive and trigger the + * socket to become readable. + */ + pfd.fd = ss2; + pfd.events = POLLIN; + rc = poll(&pfd, 1, 60000); + ATF_CHECK_EQ(1, rc); + ATF_CHECK_EQ(POLLIN, pfd.revents); + + close(ss2); + close(ss); +} + +ATF_TC_WITHOUT_HEAD(socket_afinet_poll_rdhup); +ATF_TC_BODY(socket_afinet_poll_rdhup, tc) +{ + int ss, ss2, cs, rc; + struct sockaddr_in sin; + struct pollfd pfd; + char buffer; + int one = 1; + + /* Verify that server sees POLLRDHUP if it asks for it. */ + + /* Server setup. */ + ss = socket(PF_INET, SOCK_STREAM, 0); + ATF_CHECK(ss >= 0); + rc = setsockopt(ss, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); + ATF_CHECK_EQ(0, rc); + bzero(&sin, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_len = sizeof(sin); + sin.sin_port = htons(6666); + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + rc = bind(ss, (struct sockaddr *)&sin, sizeof(sin)); + ATF_CHECK_EQ(0, rc); + rc = listen(ss, 1); + ATF_CHECK_EQ(0, rc); + + /* Client connects, server accepts. */ + cs = socket(PF_INET, SOCK_STREAM, 0); + ATF_CHECK(cs >= 0); + rc = connect(cs, (struct sockaddr *)&sin, sizeof(sin)); + ATF_CHECK_EQ(0, rc); + ss2 = accept(ss, NULL, NULL); + ATF_CHECK(ss2 >= 0); + + /* Server can write, so sees POLLOUT. */ + pfd.fd = ss2; + pfd.events = POLLIN | POLLOUT | POLLRDHUP; + rc = poll(&pfd, 1, 0); + ATF_CHECK_EQ(1, rc); + ATF_CHECK_EQ(POLLOUT, pfd.revents); + + /* Client writes two bytes, server reads only one of them. */ + rc = write(cs, "xx", 2); + ATF_CHECK_EQ(2, rc); + rc = read(ss2, &buffer, 1); + ATF_CHECK_EQ(1, rc); + + /* Server can read, so sees POLLIN. */ + pfd.fd = ss2; + pfd.events = POLLIN | POLLOUT | POLLRDHUP; + rc = poll(&pfd, 1, 0); + ATF_CHECK_EQ(1, rc); + ATF_CHECK_EQ(POLLIN | POLLOUT, pfd.revents); + + /* Client closes socket! */ + rc = close(cs); + ATF_CHECK_EQ(0, rc); + + /* + * Server sees Linux-style POLLRDHUP. Note that this is the case even + * though one byte of data remains unread. + * + * This races against the delivery of FIN caused by the close() above. + * Sometimes (more likely when run under truss or if another system + * call is added in between) it hits the path where sopoll_generic() + * immediately sees SBS_CANTRCVMORE, and sometimes it sleeps with flag + * SB_SEL so that it's woken up almost immediately and runs again, + * which is why we need a non-zero timeout here. + */ + pfd.fd = ss2; + pfd.events = POLLRDHUP; + rc = poll(&pfd, 1, 60000); + ATF_CHECK_EQ(1, rc); + ATF_CHECK_EQ(POLLRDHUP, pfd.revents); + + close(ss2); + close(ss); +} + ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, socket_afinet); ATF_TP_ADD_TC(tp, socket_afinet_bind_zero); ATF_TP_ADD_TC(tp, socket_afinet_bind_ok); + ATF_TP_ADD_TC(tp, socket_afinet_poll_no_rdhup); + ATF_TP_ADD_TC(tp, socket_afinet_poll_rdhup); return atf_no_error(); } diff --git a/tools/regression/poll/14/sockpoll.out b/tools/regression/poll/14/sockpoll.out new file mode 100644 --- /dev/null +++ b/tools/regression/poll/14/sockpoll.out @@ -0,0 +1,22 @@ +1..18 +ok 1 state initial 0: expected POLLOUT; got POLLOUT +ok 2 state initial 1: expected POLLOUT; got POLLOUT +ok 3 state after large write: expected 0; got 0 +ok 4 state other side after large write: expected POLLIN | POLLOUT; got POLLIN | POLLOUT +ok 5 state other side after close: expected POLLIN | POLLHUP; got POLLIN | POLLHUP +not ok 6 state other side after reading input: expected POLLHUP; got POLLIN | POLLHUP +ok 7 state after shutdown(SHUT_WR): expected POLLOUT; got POLLOUT +ok 8 state other side after shutdown(SHUT_WR): expected POLLIN | POLLOUT; got POLLIN | POLLOUT +ok 9 state other side after reading EOF: expected POLLIN | POLLOUT; got POLLIN | POLLOUT +ok 10 state after data from other side: expected POLLIN | POLLOUT; got POLLIN | POLLOUT +ok 11 state after writing: expected POLLIN; got POLLIN +ok 12 state after second shutdown: expected POLLIN | POLLHUP; got POLLIN | POLLHUP +not ok 13 state after second shutdown: expected POLLHUP; got POLLIN | POLLHUP +not ok 14 state after close: expected POLLHUP; got POLLIN | POLLHUP +ok 15 state after shutdown(SHUT_RD): expected POLLIN | POLLOUT; got POLLIN | POLLOUT +ok 16 state other side after shutdown(SHUT_RD): expected POLLOUT; got POLLOUT +not ok 17 state after shutdown(SHUT_WR): expected POLLHUP; got POLLIN | POLLHUP +ok 18 state other side after shutdown(SHUT_WR): expected POLLIN | POLLOUT; got POLLIN | POLLOUT +ok 19 state other side after shutdown(SHUT_RD): expected POLLOUT; got POLLOUT +ok 20 state other side after write: expected POLLIN | POLLOUT; got POLLIN | POLLOUT +ok 21 state other side after shutdown(SHUT_WR): expected POLLIN | POLLOUT | POLLRDHUP; got POLLIN | POLLOUT | POLLRDHUP diff --git a/tools/regression/poll/l/sockpoll.out b/tools/regression/poll/l/sockpoll.out new file mode 100644 --- /dev/null +++ b/tools/regression/poll/l/sockpoll.out @@ -0,0 +1,22 @@ +1..18 +ok 1 state initial 0: expected POLLOUT; got POLLOUT +ok 2 state initial 1: expected POLLOUT; got POLLOUT +ok 3 state after large write: expected 0; got 0 +ok 4 state other side after large write: expected POLLIN | POLLOUT; got POLLIN | POLLOUT +not ok 5 state other side after close: expected POLLIN | POLLHUP; got POLLIN | POLLOUT | POLLHUP +not ok 6 state other side after reading input: expected POLLHUP; got POLLIN | POLLOUT | POLLHUP +ok 7 state after shutdown(SHUT_WR): expected POLLOUT; got POLLOUT +ok 8 state other side after shutdown(SHUT_WR): expected POLLIN | POLLOUT; got POLLIN | POLLOUT +ok 9 state other side after reading EOF: expected POLLIN | POLLOUT; got POLLIN | POLLOUT +ok 10 state after data from other side: expected POLLIN | POLLOUT; got POLLIN | POLLOUT +ok 11 state after writing: expected POLLIN; got POLLIN +not ok 12 state after second shutdown: expected POLLIN | POLLHUP; got POLLIN | POLLOUT | POLLHUP +not ok 13 state after second shutdown: expected POLLHUP; got POLLIN | POLLHUP +not ok 14 state after close: expected POLLHUP; got POLLIN | POLLOUT | POLLHUP | 8 +ok 15 state after shutdown(SHUT_RD): expected POLLIN | POLLOUT; got POLLIN | POLLOUT +ok 16 state other side after shutdown(SHUT_RD): expected POLLOUT; got POLLOUT +not ok 17 state after shutdown(SHUT_WR): expected POLLHUP; got POLLIN | POLLOUT | POLLHUP +not ok 18 state other side after shutdown(SHUT_WR): expected POLLIN | POLLOUT; got POLLIN | POLLOUT | POLLHUP +ok 19 state other side after shutdown(SHUT_RD): expected POLLOUT; got POLLOUT +ok 20 state other side after write: expected POLLIN | POLLOUT; got POLLIN | POLLOUT +not ok 21 state other side after shutdown(SHUT_WR): expected POLLIN | POLLOUT | POLLRDHUP; got POLLIN | POLLOUT | POLLHUP | POLLRDHUP diff --git a/tools/regression/poll/sockpoll.c b/tools/regression/poll/sockpoll.c --- a/tools/regression/poll/sockpoll.c +++ b/tools/regression/poll/sockpoll.c @@ -1,61 +1,82 @@ /* $FreeBSD$ */ -#include +#define _GNU_SOURCE /* expose POLLRDHUP when testing on Linux */ + #include #include #include #include +#include #include #include #include +#include #include -static const char * -decode_events(int events) +static void +append(char *out, size_t out_size, const char *s) +{ + size_t size = strlen(out); + + snprintf(out + size, out_size - size, "%s", s); +} + +static void +decode_events(int events, char *out, size_t out_size) { - char *ncresult; - const char *result; - - switch (events) { - case POLLIN: - result = "POLLIN"; - break; - case POLLOUT: - result = "POLLOUT"; - break; - case POLLIN | POLLOUT: - result = "POLLIN | POLLOUT"; - break; - case POLLHUP: - result = "POLLHUP"; - break; - case POLLIN | POLLHUP: - result = "POLLIN | POLLHUP"; - break; - case POLLOUT | POLLHUP: - result = "POLLOUT | POLLHUP"; - break; - case POLLIN | POLLOUT | POLLHUP: - result = "POLLIN | POLLOUT | POLLHUP"; - break; - default: - asprintf(&ncresult, "%#x", events); - result = ncresult; - break; + int unknown; + + out[0] = 0; + + if (events == 0) { + append(out, out_size, "0"); + return; + } + +#define DECODE_FLAG(x) \ + if (events & (x)) { \ + if (out[0] != 0) \ + append(out, out_size, " | "); \ + append(out, out_size, #x); \ + } + + /* Show the expected flags by name. */ + DECODE_FLAG(POLLIN); + DECODE_FLAG(POLLOUT); + DECODE_FLAG(POLLHUP); +#ifndef POLLRDHUP +#define KNOWN_FLAGS (POLLIN | POLLOUT | POLLHUP) +#else + DECODE_FLAG(POLLRDHUP); +#define KNOWN_FLAGS (POLLIN | POLLOUT | POLLHUP | POLLRDHUP); +#endif + + /* Show any unexpected bits as hex. */ + unknown = events & ~KNOWN_FLAGS; + if (unknown != 0) { + char buf[80]; + + snprintf(buf, sizeof(buf), "%s%x", out[0] != 0 ? " | " : "", + unknown); + append(out, out_size, buf); } - return (result); } static void report(int num, const char *state, int expected, int got) { + char expected_str[80]; + char got_str[80]; + + decode_events(expected, expected_str, sizeof(expected_str)); + decode_events(got, got_str, sizeof(got_str)); if (expected == got) printf("ok %-2d ", num); else printf("not ok %-2d", num); printf(" state %s: expected %s; got %s\n", - state, decode_events(expected), decode_events(got)); + state, expected_str, got_str); fflush(stdout); } @@ -198,5 +219,27 @@ close(fd[0]); close(fd[1]); +#ifdef POLLRDHUP + setup(); + pfd1.events |= POLLRDHUP; + if (shutdown(fd[0], SHUT_RD) == -1) + err(1, "shutdown"); + if (poll(&pfd1, 1, 0) == -1) + err(1, "poll"); + report(num++, "other side after shutdown(SHUT_RD)", POLLOUT, pfd1.revents); + if (write(fd[0], "x", 1) != 1) + err(1, "write"); + if (poll(&pfd1, 1, 0) == -1) + err(1, "poll"); + report(num++, "other side after write", POLLIN | POLLOUT, pfd1.revents); + if (shutdown(fd[0], SHUT_WR) == -1) + err(1, "shutdown"); + if (poll(&pfd1, 1, 0) == -1) + err(1, "poll"); + report(num++, "other side after shutdown(SHUT_WR)", POLLIN | POLLOUT | POLLRDHUP, pfd1.revents); + close(fd[0]); + close(fd[1]); +#endif + return (0); } diff --git a/usr.bin/truss/syscalls.c b/usr.bin/truss/syscalls.c --- a/usr.bin/truss/syscalls.c +++ b/usr.bin/truss/syscalls.c @@ -726,7 +726,7 @@ static struct xlat poll_flags[] = { X(POLLSTANDARD) X(POLLIN) X(POLLPRI) X(POLLOUT) X(POLLERR) X(POLLHUP) X(POLLNVAL) X(POLLRDNORM) X(POLLRDBAND) - X(POLLWRBAND) X(POLLINIGNEOF) XEND + X(POLLWRBAND) X(POLLINIGNEOF) X(POLLRDHUP) XEND }; static struct xlat sigaction_flags[] = {