xref: /freebsd/tools/regression/sockets/sblock/sblock.c (revision 8ddb146abcdf061be9f2c0db7e391697dafad85c)
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 
55 static int interrupted;
56 static void
57 signal_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  */
67 static void
68 blocking_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  */
92 static void
93 locking_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 
113 static void
114 signaller(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 
150 int
151 main(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