aboutsummaryrefslogtreecommitdiff
path: root/tests/sys/kern/unix_stream.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/sys/kern/unix_stream.c')
-rw-r--r--tests/sys/kern/unix_stream.c401
1 files changed, 396 insertions, 5 deletions
diff --git a/tests/sys/kern/unix_stream.c b/tests/sys/kern/unix_stream.c
index d57cfad020fa..bb811f78f620 100644
--- a/tests/sys/kern/unix_stream.c
+++ b/tests/sys/kern/unix_stream.c
@@ -26,14 +26,18 @@
*/
#include <sys/cdefs.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <pthread.h>
-#include <signal.h>
#include <sys/socket.h>
+#include <sys/event.h>
+#include <sys/select.h>
+#include <sys/sysctl.h>
#include <sys/un.h>
-
+#include <errno.h>
+#include <fcntl.h>
#include <stdio.h>
+#include <stdlib.h>
+#include <poll.h>
+#include <pthread.h>
+#include <pthread_np.h>
#include <atf-c.h>
@@ -49,6 +53,18 @@ do_socketpair(int *sv)
ATF_REQUIRE(sv[0] != sv[1]);
}
+static u_long
+getsendspace(void)
+{
+ u_long sendspace;
+
+ ATF_REQUIRE_MSG(sysctlbyname("net.local.stream.sendspace", &sendspace,
+ &(size_t){sizeof(u_long)}, NULL, 0) != -1,
+ "sysctl net.local.stream.sendspace failed: %s", strerror(errno));
+
+ return (sendspace);
+}
+
/* getpeereid(3) should work with stream sockets created via socketpair(2) */
ATF_TC_WITHOUT_HEAD(getpeereid);
ATF_TC_BODY(getpeereid, tc)
@@ -86,11 +102,386 @@ ATF_TC_BODY(send_0, tc)
close(sv[1]);
}
+struct check_ctx;
+typedef void check_func_t(struct check_ctx *);
+struct check_ctx {
+ check_func_t *method;
+ int sv[2];
+ bool timeout;
+ union {
+ enum { SELECT_RD, SELECT_WR } select_what;
+ short poll_events;
+ short kev_filter;
+ };
+ int nfds;
+ union {
+ short poll_revents;
+ unsigned short kev_flags;
+ };
+};
+
+static void
+check_select(struct check_ctx *ctx)
+{
+ fd_set fds;
+ int nfds;
+
+ FD_ZERO(&fds);
+ FD_SET(ctx->sv[0], &fds);
+ nfds = select(ctx->sv[0] + 1,
+ ctx->select_what == SELECT_RD ? &fds : NULL,
+ ctx->select_what == SELECT_WR ? &fds : NULL,
+ NULL,
+ ctx->timeout ? &(struct timeval){.tv_usec = 1000} : NULL);
+ ATF_REQUIRE_MSG(nfds == ctx->nfds,
+ "select() returns %d errno %d", nfds, errno);
+}
+
+static void
+check_poll(struct check_ctx *ctx)
+{
+ struct pollfd pfd[1];
+ int nfds;
+
+ pfd[0] = (struct pollfd){
+ .fd = ctx->sv[0],
+ .events = ctx->poll_events,
+ };
+ nfds = poll(pfd, 1, ctx->timeout ? 1 : INFTIM);
+ ATF_REQUIRE_MSG(nfds == ctx->nfds,
+ "poll() returns %d errno %d", nfds, errno);
+ ATF_REQUIRE((pfd[0].revents & ctx->poll_revents) == ctx->poll_revents);
+}
+
+static void
+check_kevent(struct check_ctx *ctx)
+{
+ struct kevent kev;
+ int nfds, kq;
+
+ ATF_REQUIRE(kq = kqueue());
+ EV_SET(&kev, ctx->sv[0], ctx->kev_filter, EV_ADD, 0, 0, NULL);
+ nfds = kevent(kq, &kev, 1, NULL, 0, NULL);
+ ATF_REQUIRE_MSG(nfds == 0,
+ "kevent() returns %d errno %d", nfds, errno);
+ nfds = kevent(kq, NULL, 0, &kev, 1, ctx->timeout ?
+ &(struct timespec){.tv_nsec = 1000000} : NULL);
+ ATF_REQUIRE_MSG(nfds == ctx->nfds,
+ "kevent() returns %d errno %d", nfds, errno);
+ ATF_REQUIRE(kev.ident == (uintptr_t)ctx->sv[0] &&
+ kev.filter == ctx->kev_filter &&
+ (kev.flags & ctx->kev_flags) == ctx->kev_flags);
+ close(kq);
+}
+
+static void
+full_socketpair(int *sv)
+{
+ void *buf;
+ u_long sendspace;
+
+ sendspace = getsendspace();
+ ATF_REQUIRE((buf = malloc(sendspace)) != NULL);
+ do_socketpair(sv);
+ ATF_REQUIRE(fcntl(sv[0], F_SETFL, O_NONBLOCK) != -1);
+ do {} while (send(sv[0], buf, sendspace, 0) == (ssize_t)sendspace);
+ ATF_REQUIRE(errno == EAGAIN);
+ ATF_REQUIRE(fcntl(sv[0], F_SETFL, 0) != -1);
+ free(buf);
+}
+
+static void *
+pthread_wrap(void *arg)
+{
+ struct check_ctx *ctx = arg;
+
+ ctx->method(ctx);
+
+ return (NULL);
+}
+
+/*
+ * Launch a thread that would block in event mech and return it.
+ */
+static pthread_t
+pthread_create_blocked(struct check_ctx *ctx)
+{
+ pthread_t thr;
+
+ ctx->timeout = false;
+ ctx->nfds = 1;
+ ATF_REQUIRE(pthread_create(&thr, NULL, pthread_wrap, ctx) == 0);
+
+ /* Sleep a bit to make sure that thread is put to sleep. */
+ usleep(10000);
+ ATF_REQUIRE(pthread_peekjoin_np(thr, NULL) == EBUSY);
+
+ return (thr);
+}
+
+static void
+full_writability_check(struct check_ctx *ctx)
+{
+ pthread_t thr;
+ void *buf;
+ u_long space;
+
+ space = getsendspace() / 2;
+ ATF_REQUIRE((buf = malloc(space)) != NULL);
+
+ /* First check with timeout, expecting 0 fds returned. */
+ ctx->timeout = true;
+ ctx->nfds = 0;
+ ctx->method(ctx);
+
+ thr = pthread_create_blocked(ctx);
+
+ /* Read some data and re-check, the fd is expected to be returned. */
+ ATF_REQUIRE(read(ctx->sv[1], buf, space) == (ssize_t)space);
+
+ /* Now check that thread was successfully woken up and exited. */
+ ATF_REQUIRE(pthread_join(thr, NULL) == 0);
+
+ /* Extra check repeating what joined thread already did. */
+ ctx->method(ctx);
+
+ close(ctx->sv[0]);
+ close(ctx->sv[1]);
+ free(buf);
+}
+
+/*
+ * Make sure that a full socket is not reported as writable by event APIs.
+ */
+ATF_TC_WITHOUT_HEAD(full_writability_select);
+ATF_TC_BODY(full_writability_select, tc)
+{
+ struct check_ctx ctx = {
+ .method = check_select,
+ .select_what = SELECT_WR,
+ };
+
+ full_socketpair(ctx.sv);
+ full_writability_check(&ctx);
+ close(ctx.sv[0]);
+ close(ctx.sv[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(full_writability_poll);
+ATF_TC_BODY(full_writability_poll, tc)
+{
+ struct check_ctx ctx = {
+ .method = check_poll,
+ .poll_events = POLLOUT | POLLWRNORM,
+ };
+
+ full_socketpair(ctx.sv);
+ full_writability_check(&ctx);
+ close(ctx.sv[0]);
+ close(ctx.sv[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(full_writability_kevent);
+ATF_TC_BODY(full_writability_kevent, tc)
+{
+ struct check_ctx ctx = {
+ .method = check_kevent,
+ .kev_filter = EVFILT_WRITE,
+ };
+
+ full_socketpair(ctx.sv);
+ full_writability_check(&ctx);
+ close(ctx.sv[0]);
+ close(ctx.sv[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(connected_writability);
+ATF_TC_BODY(connected_writability, tc)
+{
+ struct check_ctx ctx = {
+ .timeout = true,
+ .nfds = 1,
+ };
+
+ do_socketpair(ctx.sv);
+
+ ctx.select_what = SELECT_WR;
+ check_select(&ctx);
+ ctx.poll_events = POLLOUT | POLLWRNORM;
+ check_poll(&ctx);
+ ctx.kev_filter = EVFILT_WRITE;
+ check_kevent(&ctx);
+
+ close(ctx.sv[0]);
+ close(ctx.sv[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(unconnected_writability);
+ATF_TC_BODY(unconnected_writability, tc)
+{
+ struct check_ctx ctx = {
+ .timeout = true,
+ .nfds = 0,
+ };
+
+ ATF_REQUIRE((ctx.sv[0] = socket(PF_LOCAL, SOCK_STREAM, 0)) > 0);
+
+ ctx.select_what = SELECT_WR;
+ check_select(&ctx);
+ ctx.poll_events = POLLOUT | POLLWRNORM;
+ check_poll(&ctx);
+ ctx.kev_filter = EVFILT_WRITE;
+ check_kevent(&ctx);
+
+ close(ctx.sv[0]);
+}
+
+ATF_TC_WITHOUT_HEAD(peerclosed_writability);
+ATF_TC_BODY(peerclosed_writability, tc)
+{
+ struct check_ctx ctx = {
+ .timeout = false,
+ .nfds = 1,
+ };
+
+ do_socketpair(ctx.sv);
+ close(ctx.sv[1]);
+
+ ctx.select_what = SELECT_WR;
+ check_select(&ctx);
+ ctx.poll_events = POLLOUT | POLLWRNORM;
+ check_poll(&ctx);
+ ctx.kev_filter = EVFILT_WRITE;
+ ctx.kev_flags = EV_EOF;
+ check_kevent(&ctx);
+
+ close(ctx.sv[0]);
+}
+
+ATF_TC_WITHOUT_HEAD(peershutdown_writability);
+ATF_TC_BODY(peershutdown_writability, tc)
+{
+ struct check_ctx ctx = {
+ .timeout = false,
+ .nfds = 1,
+ };
+
+ do_socketpair(ctx.sv);
+ shutdown(ctx.sv[1], SHUT_RD);
+
+ ctx.select_what = SELECT_WR;
+ check_select(&ctx);
+ ctx.poll_events = POLLOUT | POLLWRNORM;
+ check_poll(&ctx);
+ /*
+ * XXXGL: historically unix(4) sockets were not reporting peer's
+ * shutdown(SHUT_RD) as our EV_EOF. The kevent(2) manual page says
+ * "filter will set EV_EOF when the reader disconnects", which is hard
+ * to interpret unambigously. For now leave the historic behavior,
+ * but we may want to change that in uipc_usrreq.c:uipc_filt_sowrite(),
+ * and then this test will also expect EV_EOF in returned flags.
+ */
+ ctx.kev_filter = EVFILT_WRITE;
+ check_kevent(&ctx);
+
+ close(ctx.sv[0]);
+ close(ctx.sv[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(peershutdown_readability);
+ATF_TC_BODY(peershutdown_readability, tc)
+{
+ struct check_ctx ctx = {
+ .timeout = false,
+ .nfds = 1,
+ };
+ ssize_t readsz;
+ char c;
+
+ do_socketpair(ctx.sv);
+ shutdown(ctx.sv[1], SHUT_WR);
+
+ /*
+ * The other side should flag as readable in select(2) to allow it to
+ * read(2) and observe EOF. Ensure that both poll(2) and select(2)
+ * are consistent here.
+ */
+ ctx.select_what = SELECT_RD;
+ check_select(&ctx);
+ ctx.poll_events = POLLIN | POLLRDNORM;
+ check_poll(&ctx);
+
+ /*
+ * Also check that read doesn't block.
+ */
+ readsz = read(ctx.sv[0], &c, sizeof(c));
+ ATF_REQUIRE_INTEQ(0, readsz);
+
+ close(ctx.sv[0]);
+ close(ctx.sv[1]);
+}
+
+static void
+peershutdown_wakeup(struct check_ctx *ctx)
+{
+ pthread_t thr;
+
+ ctx->timeout = false;
+ ctx->nfds = 1;
+
+ do_socketpair(ctx->sv);
+ thr = pthread_create_blocked(ctx);
+ shutdown(ctx->sv[1], SHUT_WR);
+ ATF_REQUIRE(pthread_join(thr, NULL) == 0);
+
+ close(ctx->sv[0]);
+ close(ctx->sv[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(peershutdown_wakeup_select);
+ATF_TC_BODY(peershutdown_wakeup_select, tc)
+{
+ peershutdown_wakeup(&(struct check_ctx){
+ .method = check_select,
+ .select_what = SELECT_RD,
+ });
+}
+
+ATF_TC_WITHOUT_HEAD(peershutdown_wakeup_poll);
+ATF_TC_BODY(peershutdown_wakeup_poll, tc)
+{
+ peershutdown_wakeup(&(struct check_ctx){
+ .method = check_poll,
+ .poll_events = POLLIN | POLLRDNORM | POLLRDHUP,
+ .poll_revents = POLLRDHUP,
+ });
+}
+
+ATF_TC_WITHOUT_HEAD(peershutdown_wakeup_kevent);
+ATF_TC_BODY(peershutdown_wakeup_kevent, tc)
+{
+ peershutdown_wakeup(&(struct check_ctx){
+ .method = check_kevent,
+ .kev_filter = EVFILT_READ,
+ .kev_flags = EV_EOF,
+ });
+}
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, getpeereid);
ATF_TP_ADD_TC(tp, send_0);
+ ATF_TP_ADD_TC(tp, connected_writability);
+ ATF_TP_ADD_TC(tp, unconnected_writability);
+ ATF_TP_ADD_TC(tp, full_writability_select);
+ ATF_TP_ADD_TC(tp, full_writability_poll);
+ ATF_TP_ADD_TC(tp, full_writability_kevent);
+ ATF_TP_ADD_TC(tp, peerclosed_writability);
+ ATF_TP_ADD_TC(tp, peershutdown_writability);
+ ATF_TP_ADD_TC(tp, peershutdown_readability);
+ ATF_TP_ADD_TC(tp, peershutdown_wakeup_select);
+ ATF_TP_ADD_TC(tp, peershutdown_wakeup_poll);
+ ATF_TP_ADD_TC(tp, peershutdown_wakeup_kevent);
return atf_no_error();
}