xref: /freebsd/tools/regression/sockets/sblock/sblock.c (revision 59c8e88e72633afbc47a4ace0d2170d00d51f7dc)
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 
27 /*
28  * Sockets serialize I/O in each direction in order to avoid interlacing of
29  * I/O by multiple processes or threcvs recving or sending the socket.  This
30  * is done using some form of kernel lock (varies by kernel version), called
31  * "sblock" in FreeBSD.  However, to avoid unkillable processes waiting on
32  * I/O that may be entirely controlled by a remote network endpoint, that
33  * lock acquisition must be interruptible.
34  *
35  * To test this, set up a local domain stream socket pair and a set of three
36  * processes.  Two processes block in recv(), the first on sbwait (wait for
37  * I/O), and the second on the sblock waiting for the first to finish.  A
38  * third process is responsible for signalling the second process, then
39  * writing to the socket.  Depending on the error returned in the second
40  * process, we can tell whether the sblock wait was interrupted, or if
41  * instead the process only woke up when the write was performed.
42  */
43 
44 #include <sys/socket.h>
45 
46 #include <err.h>
47 #include <errno.h>
48 #include <signal.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <unistd.h>
52 
53 static int interrupted;
54 static void
55 signal_handler(int signum __unused)
56 {
57 
58 	interrupted++;
59 }
60 
61 /*
62  * Process that will perform a blocking recv on a UNIX domain socket.  This
63  * should return one byte of data.
64  */
65 static void
66 blocking_recver(int fd)
67 {
68 	ssize_t len;
69 	char ch;
70 
71 	len = recv(fd, &ch, sizeof(ch), 0);
72 	if (len < 0)
73 		err(-1, "FAIL: blocking_recver: recv");
74 	if (len == 0)
75 		errx(-1, "FAIL: blocking_recver: recv: eof");
76 	if (len != 1)
77 		errx(-1, "FAIL: blocking_recver: recv: %zd bytes", len);
78 	if (interrupted)
79 		errx(-1, "FAIL: blocking_recver: interrupted wrong pid");
80 }
81 
82 /*
83  * Process that will perform a locking recv on a UNIX domain socket.
84  *
85  * This is where we figure out if the test worked or not.  If it has failed,
86  * then recv() will return EOF, as the close() arrives before the signal,
87  * meaning that the wait for the sblock was not interrupted; if it has
88  * succeeded, we get EINTR as the signal interrupts the lock request.
89  */
90 static void
91 locking_recver(int fd)
92 {
93 	ssize_t len;
94 	char ch;
95 
96 	if (sleep(1) != 0)
97 		err(-1, "FAIL: locking_recver: sleep");
98 	len = recv(fd, &ch, sizeof(ch), 0);
99 	if (len < 0 && errno != EINTR)
100 		err(-1, "FAIL: locking_recver: recv");
101 	if (len < 0 && errno == EINTR) {
102 		fprintf(stderr, "PASS\n");
103 		exit(0);
104 	}
105 	if (len == 0)
106 		errx(-1, "FAIL: locking_recver: recv: eof");
107 	if (!interrupted)
108 		errx(-1, "FAIL: locking_recver: not interrupted");
109 }
110 
111 static void
112 signaller(pid_t locking_recver_pid, int fd)
113 {
114 	ssize_t len;
115 	char ch;
116 
117 	if (sleep(2) != 0) {
118 		warn("signaller sleep(2)");
119 		return;
120 	}
121 	if (kill(locking_recver_pid, SIGHUP) < 0) {
122 		warn("signaller kill(%d)", locking_recver_pid);
123 		return;
124 	}
125 	if (sleep(1) != 0) {
126 		warn("signaller sleep(1)");
127 		return;
128 	}
129 	len = send(fd, &ch, sizeof(ch), 0);
130 	if (len < 0) {
131 		warn("signaller send");
132 		return;
133 	}
134 	if (len != sizeof(ch)) {
135 		warnx("signaller send ret %zd", len);
136 		return;
137 	}
138 	if (close(fd) < 0) {
139 		warn("signaller close");
140 		return;
141 	}
142 	if (sleep(1) != 0) {
143 		warn("signaller sleep(1)");
144 		return;
145 	}
146 }
147 
148 int
149 main(void)
150 {
151 	int error, fds[2], recver_fd, sender_fd;
152 	pid_t blocking_recver_pid;
153 	pid_t locking_recver_pid;
154 	struct sigaction sa;
155 
156 	if (sigaction(SIGHUP, NULL, &sa) < 0)
157 		err(-1, "FAIL: sigaction(SIGHUP, NULL, &sa)");
158 
159 	sa.sa_handler = signal_handler;
160 	if (sa.sa_flags & SA_RESTART)
161 		printf("SIGHUP restartable by default (cleared)\n");
162 	sa.sa_flags &= ~SA_RESTART;
163 
164 	if (sigaction(SIGHUP, &sa, NULL) < 0)
165 		err(-1, "FAIL: sigaction(SIGHUP, &sa, NULL)");
166 
167 #if 0
168 	if (signal(SIGHUP, signal_handler) == SIG_ERR)
169 		err(-1, "FAIL: signal(SIGHUP)");
170 #endif
171 
172 	if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fds) < 0)
173 		err(-1, "FAIL: socketpair(PF_LOCAL, SOGK_STREAM, 0)");
174 
175 	sender_fd = fds[0];
176 	recver_fd = fds[1];
177 
178 	blocking_recver_pid = fork();
179 	if (blocking_recver_pid < 0)
180 		err(-1, "FAIL: fork");
181 	if (blocking_recver_pid == 0) {
182 		close(sender_fd);
183 		blocking_recver(recver_fd);
184 		exit(0);
185 	}
186 
187 	locking_recver_pid = fork();
188 	if (locking_recver_pid < 0) {
189 		error = errno;
190 		kill(blocking_recver_pid, SIGKILL);
191 		errno = error;
192 		err(-1, "FAIL: fork");
193 	}
194 	if (locking_recver_pid == 0) {
195 		close(sender_fd);
196 		locking_recver(recver_fd);
197 		exit(0);
198 	}
199 
200 	signaller(locking_recver_pid, sender_fd);
201 
202 	kill(blocking_recver_pid, SIGKILL);
203 	kill(locking_recver_pid, SIGKILL);
204 	exit(0);
205 }
206