poll(2): Add POLLRDHUP.

Teach poll(2) to support Linux-style POLLRDHUP events for sockets, if
requested.  Triggered when the remote peer shuts down writing or closes
its end.

Reviewed by:	kib
MFC after:	1 month
Differential Revision:	https://reviews.freebsd.org/D29757
This commit is contained in:
Thomas Munro 2021-04-28 21:31:38 +12:00
parent 01a856c666
commit 3aaaa2efde
5 changed files with 159 additions and 3 deletions

View File

@ -28,7 +28,7 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\" .\"
.Dd February 27, 2019 .Dd April 27, 2021
.Dt POLL 2 .Dt POLL 2
.Os .Os
.Sh NAME .Sh NAME
@ -126,6 +126,15 @@ POLLOUT
should never be present in the should never be present in the
.Fa revents .Fa revents
bitmask at the same time. 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 .It POLLNVAL
The file descriptor is not open, The file descriptor is not open,
or in capability mode the file descriptor has insufficient rights. or in capability mode the file descriptor has insufficient rights.
@ -261,6 +270,9 @@ function conforms to
The The
.Fn ppoll .Fn ppoll
is not specified by POSIX. is not specified by POSIX.
The
POLLRDHUP
flag is not specified by POSIX, but is compatible with Linux and illumos.
.Sh HISTORY .Sh HISTORY
The The
.Fn poll .Fn poll

View File

@ -3571,9 +3571,11 @@ sopoll_generic(struct socket *so, int events, struct ucred *active_cred,
revents |= POLLHUP; revents |= POLLHUP;
} }
} }
if (so->so_rcv.sb_state & SBS_CANTRCVMORE)
revents |= events & POLLRDHUP;
if (revents == 0) { if (revents == 0) {
if (events & if (events &
(POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND)) { (POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND | POLLRDHUP)) {
selrecord(td, &so->so_rdsel); selrecord(td, &so->so_rdsel);
so->so_rcv.sb_flags |= SB_SEL; so->so_rcv.sb_flags |= SB_SEL;
} }

View File

@ -71,6 +71,7 @@ struct pollfd {
#if __BSD_VISIBLE #if __BSD_VISIBLE
/* General FreeBSD extension (currently only supported for sockets): */ /* General FreeBSD extension (currently only supported for sockets): */
#define POLLINIGNEOF 0x2000 /* like POLLIN, except ignore EOF */ #define POLLINIGNEOF 0x2000 /* like POLLIN, except ignore EOF */
#define POLLRDHUP 0x4000 /* half shut down */
#endif #endif
/* /*

View File

@ -31,6 +31,7 @@ __FBSDID("$FreeBSD$");
#include <sys/errno.h> #include <sys/errno.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <poll.h>
#include <atf-c.h> #include <atf-c.h>
@ -89,12 +90,152 @@ ATF_TC_BODY(socket_afinet_bind_ok, tc)
close(sd); 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_TCS(tp)
{ {
ATF_TP_ADD_TC(tp, socket_afinet); ATF_TP_ADD_TC(tp, socket_afinet);
ATF_TP_ADD_TC(tp, socket_afinet_bind_zero); 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_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(); return atf_no_error();
} }

View File

@ -726,7 +726,7 @@ struct xlat {
static struct xlat poll_flags[] = { static struct xlat poll_flags[] = {
X(POLLSTANDARD) X(POLLIN) X(POLLPRI) X(POLLOUT) X(POLLERR) X(POLLSTANDARD) X(POLLIN) X(POLLPRI) X(POLLOUT) X(POLLERR)
X(POLLHUP) X(POLLNVAL) X(POLLRDNORM) X(POLLRDBAND) 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[] = { static struct xlat sigaction_flags[] = {