1/*-
2 * Copyright (c) 2007 Robert N. M. Watson
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD$
27 */
28
29/*
30 * Sockets serialize I/O in each direction in order to avoid interlacing of
31 * I/O by multiple processes or threcvs recving or sending the socket.  This
32 * is done using some form of kernel lock (varies by kernel version), called
33 * "sblock" in FreeBSD.  However, to avoid unkillable processes waiting on
34 * I/O that may be entirely controlled by a remote network endpoint, that
35 * lock acquisition must be interruptible.
36 *
37 * To test this, set up a local domain stream socket pair and a set of three
38 * processes.  Two processes block in recv(), the first on sbwait (wait for
39 * I/O), and the second on the sblock waiting for the first to finish.  A
40 * third process is responsible for signalling the second process, then
41 * writing to the socket.  Depending on the error returned in the second
42 * process, we can tell whether the sblock wait was interrupted, or if
43 * instead the process only woke up when the write was performed.
44 */
45
46#include <sys/socket.h>
47
48#include <err.h>
49#include <errno.h>
50#include <signal.h>
51#include <stdio.h>
52#include <stdlib.h>
53#include <unistd.h>
54
55static int interrupted;
56static void
57signal_handler(int signum __unused)
58{
59
60	interrupted++;
61}
62
63/*
64 * Process that will perform a blocking recv on a UNIX domain socket.  This
65 * should return one byte of data.
66 */
67static void
68blocking_recver(int fd)
69{
70	ssize_t len;
71	char ch;
72
73	len = recv(fd, &ch, sizeof(ch), 0);
74	if (len < 0)
75		err(-1, "FAIL: blocking_recver: recv");
76	if (len == 0)
77		errx(-1, "FAIL: blocking_recver: recv: eof");
78	if (len != 1)
79		errx(-1, "FAIL: blocking_recver: recv: %zd bytes", len);
80	if (interrupted)
81		errx(-1, "FAIL: blocking_recver: interrupted wrong pid");
82}
83
84/*
85 * Process that will perform a locking recv on a UNIX domain socket.
86 *
87 * This is where we figure out if the test worked or not.  If it has failed,
88 * then recv() will return EOF, as the close() arrives before the signal,
89 * meaning that the wait for the sblock was not interrupted; if it has
90 * succeeded, we get EINTR as the signal interrupts the lock request.
91 */
92static void
93locking_recver(int fd)
94{
95	ssize_t len;
96	char ch;
97
98	if (sleep(1) != 0)
99		err(-1, "FAIL: locking_recver: sleep");
100	len = recv(fd, &ch, sizeof(ch), 0);
101	if (len < 0 && errno != EINTR)
102		err(-1, "FAIL: locking_recver: recv");
103	if (len < 0 && errno == EINTR) {
104		fprintf(stderr, "PASS\n");
105		exit(0);
106	}
107	if (len == 0)
108		errx(-1, "FAIL: locking_recver: recv: eof");
109	if (!interrupted)
110		errx(-1, "FAIL: locking_recver: not interrupted");
111}
112
113static void
114signaller(pid_t locking_recver_pid, int fd)
115{
116	ssize_t len;
117	char ch;
118
119	if (sleep(2) != 0) {
120		warn("signaller sleep(2)");
121		return;
122	}
123	if (kill(locking_recver_pid, SIGHUP) < 0) {
124		warn("signaller kill(%d)", locking_recver_pid);
125		return;
126	}
127	if (sleep(1) != 0) {
128		warn("signaller sleep(1)");
129		return;
130	}
131	len = send(fd, &ch, sizeof(ch), 0);
132	if (len < 0) {
133		warn("signaller send");
134		return;
135	}
136	if (len != sizeof(ch)) {
137		warnx("signaller send ret %zd", len);
138		return;
139	}
140	if (close(fd) < 0) {
141		warn("signaller close");
142		return;
143	}
144	if (sleep(1) != 0) {
145		warn("signaller sleep(1)");
146		return;
147	}
148}
149
150int
151main(void)
152{
153	int error, fds[2], recver_fd, sender_fd;
154	pid_t blocking_recver_pid;
155	pid_t locking_recver_pid;
156	struct sigaction sa;
157
158	if (sigaction(SIGHUP, NULL, &sa) < 0)
159		err(-1, "FAIL: sigaction(SIGHUP, NULL, &sa)");
160
161	sa.sa_handler = signal_handler;
162	if (sa.sa_flags & SA_RESTART)
163		printf("SIGHUP restartable by default (cleared)\n");
164	sa.sa_flags &= ~SA_RESTART;
165
166	if (sigaction(SIGHUP, &sa, NULL) < 0)
167		err(-1, "FAIL: sigaction(SIGHUP, &sa, NULL)");
168
169#if 0
170	if (signal(SIGHUP, signal_handler) == SIG_ERR)
171		err(-1, "FAIL: signal(SIGHUP)");
172#endif
173
174	if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fds) < 0)
175		err(-1, "FAIL: socketpair(PF_LOCAL, SOGK_STREAM, 0)");
176
177	sender_fd = fds[0];
178	recver_fd = fds[1];
179
180	blocking_recver_pid = fork();
181	if (blocking_recver_pid < 0)
182		err(-1, "FAIL: fork");
183	if (blocking_recver_pid == 0) {
184		close(sender_fd);
185		blocking_recver(recver_fd);
186		exit(0);
187	}
188
189	locking_recver_pid = fork();
190	if (locking_recver_pid < 0) {
191		error = errno;
192		kill(blocking_recver_pid, SIGKILL);
193		errno = error;
194		err(-1, "FAIL: fork");
195	}
196	if (locking_recver_pid == 0) {
197		close(sender_fd);
198		locking_recver(recver_fd);
199		exit(0);
200	}
201
202	signaller(locking_recver_pid, sender_fd);
203
204	kill(blocking_recver_pid, SIGKILL);
205	kill(locking_recver_pid, SIGKILL);
206	exit(0);
207}
208