xref: /freebsd/tests/sys/fifo/fifo_open.c (revision b3e7694832e81d7a904a10f525f8797b753bf0d3)
1 /*-
2  * Copyright (c) 2005 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 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <sys/wait.h>
30 
31 #include <err.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <limits.h>
35 #include <signal.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40 
41 /*
42  * Regression test to exercise various POSIX-defined parts of fifo behavior
43  * described for open(2):
44  *
45  * O_NONBLOCK
46  * When opening a FIFO with O_RDONLY or O_WRONLY set:
47  *
48  * - If O_NONBLOCK is set, an open() for reading-only shall return without
49  *   delay. An open() for writing-only shall return an error if no process
50  *   currently has the file open for reading.
51  *
52  * - If O_NONBLOCK is clear, an open() for reading-only shall block the
53  *   calling thread until a thread opens the file for writing. An open()
54  *   for writing-only shall block the calling thread until a thread opens
55  *   the file for reading.
56  *
57  * When opening a block special or character special file that supports
58  * non-blocking opens:
59  *
60  * - If O_NONBLOCK is set, the open() function shall return without blocking
61  *   for the device to be ready or available. Subsequent behavior of the
62  *   device is device-specific.
63  *
64  * - If O_NONBLOCK is clear, the open() function shall block the calling
65  *   thread until the device is ready or available before returning.
66  *
67  * Special errors:
68  *
69  * [ENXIO]
70  * O_NONBLOCK is set, the named file is a FIFO, O_WRONLY is set, and no
71  * process has the file open for reading.
72  */
73 
74 /*
75  * In order to test blocking/non-blocking behavior, test processes must
76  * potentially block themselves until released.  As bugs in blocking result
77  * in processes that won't un-block, we must sacrifice a process to the task,
78  * watching and potentially killing it after a time-out.  The main test
79  * process is never used to open or act directly on a fifo (other than to
80  * create or unlink it) in order to avoid the main test process being
81  * blocked.
82  */
83 
84 /*
85  * All activity occurs within a temporary directory created early in the
86  * test.
87  */
88 static char	temp_dir[PATH_MAX];
89 
90 static void __unused
atexit_temp_dir(void)91 atexit_temp_dir(void)
92 {
93 
94 	rmdir(temp_dir);
95 }
96 
97 /*
98  * Run a function in a particular test process.
99  */
100 static int
run_in_process(int (* func)(void),pid_t * pidp,const char * errstr)101 run_in_process(int (*func)(void), pid_t *pidp, const char *errstr)
102 {
103 	pid_t pid;
104 
105 	pid = fork();
106 	if (pid < 0) {
107 		warn("%s: run_in_process: fork", errstr);
108 		return (-1);
109 	}
110 
111 	if (pid == 0)
112 		exit(func());
113 
114 	if (pidp != NULL)
115 		*pidp = pid;
116 
117 	return (0);
118 }
119 
120 /*
121  * Wait for a process on a timeout, and if the timeout expires, kill the
122  * process.  Test each second rather than waiting the full timeout at once to
123  * minimize the amount of time spent hanging around unnecessarily.
124  */
125 static int
wait_and_timeout(pid_t pid,int timeout,int * status,const char * errstr)126 wait_and_timeout(pid_t pid, int timeout, int *status, const char *errstr)
127 {
128 	pid_t wpid;
129 	int i;
130 
131 	/*
132 	 * Count up to the timeout, but do a non-hanging waitpid() after each
133 	 * second so we can avoid waiting a lot of extra time.
134 	 */
135 	for (i = 0; i < timeout; i++) {
136 		wpid = waitpid(pid, status, WNOHANG);
137 		if (wpid < 0) {
138 			warn("%s: wait_and_timeout: waitpid %d", errstr, pid);
139 			return (-1);
140 		}
141 
142 		if (wpid == pid)
143 			return (0);
144 
145 		sleep(1);
146 	}
147 
148 	wpid = waitpid(pid, status, WNOHANG);
149 	if (wpid < 0) {
150 		warn("%s: wait_and_timeout: waitpid %d", errstr, pid);
151 		return (-1);
152 	}
153 
154 	if (wpid == pid)
155 		return (0);
156 
157 	if (kill(pid, SIGTERM) < 0) {
158 		warn("%s: wait_and_timeout: kill %d", errstr, pid);
159 		return (-1);
160 	}
161 
162 	wpid = waitpid(pid, status, 0);
163 	if (wpid < 0) {
164 		warn("%s: wait_and_timeout: waitpid %d", errstr, pid);
165 		return (-1);
166 	}
167 
168 	if (wpid != pid) {
169 		warn("%s: waitpid: returned %d not %d", errstr, wpid, pid);
170 		return (-1);
171 	}
172 
173 	warnx("%s: process blocked", errstr);
174 	return (-1);
175 }
176 
177 static int
non_blocking_open_reader(void)178 non_blocking_open_reader(void)
179 {
180 	int fd;
181 
182 	fd = open("testfifo", O_RDONLY | O_NONBLOCK);
183 	if (fd < 0)
184 		return (errno);
185 	close(fd);
186 
187 	return (0);
188 }
189 
190 static int
non_blocking_open_writer(void)191 non_blocking_open_writer(void)
192 {
193 	int fd;
194 
195 	fd = open("testfifo", O_WRONLY | O_NONBLOCK);
196 	if (fd < 0)
197 		return (errno);
198 	close(fd);
199 
200 	return (0);
201 }
202 
203 static int
blocking_open_reader(void)204 blocking_open_reader(void)
205 {
206 	int fd;
207 
208 	fd = open("testfifo", O_RDONLY);
209 	if (fd < 0)
210 		return (errno);
211 	close(fd);
212 
213 	return (0);
214 }
215 
216 static int
blocking_open_writer(void)217 blocking_open_writer(void)
218 {
219 	int fd;
220 
221 	fd = open("testfifo", O_WRONLY);
222 	if (fd < 0)
223 		return (errno);
224 	close(fd);
225 
226 	return (0);
227 }
228 
229 static void
test_blocking_reader(void)230 test_blocking_reader(void)
231 {
232 	pid_t reader_pid, writer_pid, wpid;
233 	int error, status;
234 
235 	if (mkfifo("testfifo", 0600) < 0)
236 		err(-1, "test_blocking_reader: mkfifo: testfifo");
237 
238 	/*
239 	 * Block a process in opening the fifo.
240 	 */
241 	if (run_in_process(blocking_open_reader, &reader_pid,
242 	    "test_blocking_reader: blocking_open_reader") < 0) {
243 		(void)unlink("testfifo");
244 		exit(-1);
245 	}
246 
247 	/*
248 	 * Test that it blocked.
249 	 */
250 	sleep(5);
251 	wpid = waitpid(reader_pid, &status, WNOHANG);
252 	if (wpid < 0) {
253 		error = errno;
254 		(void)unlink("testfifo");
255 		errno = error;
256 		err(-1, "test_blocking_reader: waitpid %d", reader_pid);
257 	}
258 
259 	if (wpid != 0 && wpid != reader_pid) {
260 		(void)unlink("testfifo");
261 		errx(-1, "test_blocking_reader: waitpid %d returned %d",
262 		    reader_pid, wpid);
263 	}
264 
265 	if (wpid == reader_pid) {
266 		(void)unlink("testfifo");
267 		errx(-1, "test_blocking_reader: blocking child didn't "
268 		    "block");
269 	}
270 
271 	/*
272 	 * Unblock the blocking reader.
273 	 */
274 	if (run_in_process(blocking_open_writer, &writer_pid,
275 	    "test_blocking_reader: blocking_open_writer") < 0) {
276 		(void)unlink("testfifo");
277 		(void)kill(reader_pid, SIGTERM);
278 		(void)waitpid(reader_pid, &status, 0);
279 		exit(-1);
280 	}
281 
282 	/*
283 	 * Make sure both processes exited quickly (<1 second) to make sure
284 	 * they didn't block, and GC.
285 	 */
286 	if (wait_and_timeout(reader_pid, 1, &status,
287 	    "test_blocking_reader: blocking_open_reader") < 0) {
288 		(void)unlink("testinfo");
289 		(void)kill(reader_pid, SIGTERM);
290 		(void)kill(writer_pid, SIGTERM);
291 		exit(-1);
292 	}
293 
294 	if (wait_and_timeout(writer_pid, 1, &status,
295 	    "test_blocking_reader: blocking_open_writer") < 0) {
296 		(void)unlink("testinfo");
297 		(void)kill(writer_pid, SIGTERM);
298 		exit(-1);
299 	}
300 
301 	if (unlink("testfifo") < 0)
302 		err(-1, "test_blocking_reader: unlink: testfifo");
303 }
304 static void
test_blocking_writer(void)305 test_blocking_writer(void)
306 {
307 	pid_t reader_pid, writer_pid, wpid;
308 	int error, status;
309 
310 	if (mkfifo("testfifo", 0600) < 0)
311 		err(-1, "test_blocking_writer: mkfifo: testfifo");
312 
313 	/*
314 	 * Block a process in opening the fifo.
315 	 */
316 	if (run_in_process(blocking_open_writer, &writer_pid,
317 	    "test_blocking_writer: blocking_open_writer") < 0) {
318 		(void)unlink("testfifo");
319 		exit(-1);
320 	}
321 
322 	/*
323 	 * Test that it blocked.
324 	 */
325 	sleep(5);
326 	wpid = waitpid(writer_pid, &status, WNOHANG);
327 	if (wpid < 0) {
328 		error = errno;
329 		(void)unlink("testfifo");
330 		errno = error;
331 		err(-1, "test_blocking_writer: waitpid %d", writer_pid);
332 	}
333 
334 	if (wpid != 0 && wpid != writer_pid) {
335 		(void)unlink("testfifo");
336 		errx(-1, "test_blocking_writer: waitpid %d returned %d",
337 		    writer_pid, wpid);
338 	}
339 
340 	if (wpid == writer_pid) {
341 		(void)unlink("testfifo");
342 		errx(-1, "test_blocking_writer: blocking child didn't "
343 		    "block");
344 	}
345 
346 	/*
347 	 * Unblock the blocking writer.
348 	 */
349 	if (run_in_process(blocking_open_reader, &reader_pid,
350 	    "test_blocking_writer: blocking_open_reader") < 0) {
351 		(void)unlink("testfifo");
352 		(void)kill(writer_pid, SIGTERM);
353 		(void)waitpid(writer_pid, &status, 0);
354 		exit(-1);
355 	}
356 
357 	/*
358 	 * Make sure both processes exited quickly (<1 second) to make sure
359 	 * they didn't block, and GC.
360 	 */
361 	if (wait_and_timeout(writer_pid, 1, &status,
362 	    "test_blocking_writer: blocking_open_writer") < 0) {
363 		(void)unlink("testinfo");
364 		(void)kill(writer_pid, SIGTERM);
365 		(void)kill(reader_pid, SIGTERM);
366 		(void)waitpid(writer_pid, &status, 0);
367 		(void)waitpid(reader_pid, &status, 0);
368 		exit(-1);
369 	}
370 
371 	if (wait_and_timeout(reader_pid, 1, &status,
372 	    "test_blocking_writer: blocking_open_reader") < 0) {
373 		(void)unlink("testinfo");
374 		(void)kill(reader_pid, SIGTERM);
375 		(void)waitpid(reader_pid, &status, 0);
376 		exit(-1);
377 	}
378 
379 	if (unlink("testfifo") < 0)
380 		err(-1, "test_blocking_writer: unlink: testfifo");
381 }
382 
383 static void
test_non_blocking_reader(void)384 test_non_blocking_reader(void)
385 {
386 	int status;
387 	pid_t pid;
388 
389 	if (mkfifo("testfifo", 0600) < 0)
390 		err(-1, "test_non_blocking_reader: mkfifo: testfifo");
391 
392 	if (run_in_process(non_blocking_open_reader, &pid,
393 	    "test_non_blocking_reader: non_blocking_open_reader") < 0) {
394 		(void)unlink("testfifo");
395 		exit(-1);
396 	}
397 
398 	status = -1;
399 	if (wait_and_timeout(pid, 5, &status,
400 	    "test_non_blocking_reader: non_blocking_open_reader") < 0) {
401 		(void)unlink("testfifo");
402 		exit(-1);
403 	}
404 
405 	if (WEXITSTATUS(status) != 0) {
406 		(void)unlink("testfifo");
407 		errno = WEXITSTATUS(status);
408 		err(-1, "test_non_blocking_reader: "
409 		    "non_blocking_open_reader: open: testfifo");
410 	}
411 
412 	if (unlink("testfifo") < 0)
413 		err(-1, "test_non_blocking_reader: unlink: testfifo");
414 }
415 
416 static void
test_non_blocking_writer(void)417 test_non_blocking_writer(void)
418 {
419 	int status;
420 	pid_t pid;
421 
422 	if (mkfifo("testfifo", 0600) < 0)
423 		err(-1, "test_non_blocking_writer: mkfifo: testfifo");
424 
425 	if (run_in_process(non_blocking_open_writer, &pid,
426 	    "test_non_blocking_writer: non_blocking_open_writer") < 0) {
427 		(void)unlink("testfifo");
428 		exit(-1);
429 	}
430 
431 	status = -1;
432 	if (wait_and_timeout(pid, 5, &status,
433 	    "test_non_blocking_writer: non_blocking_open_writer") < 0) {
434 		(void)unlink("testfifo");
435 		exit(-1);
436 	}
437 
438 	if (WEXITSTATUS(status) != ENXIO) {
439 		(void)unlink("testfifo");
440 
441 		errno = WEXITSTATUS(status);
442 		if (errno == 0)
443 			errx(-1, "test_non_blocking_writer: "
444 			    "non_blocking_open_writer: open succeeded");
445 		err(-1, "test_non_blocking_writer: "
446 		    "non_blocking_open_writer: open: testfifo");
447 	}
448 
449 	if (unlink("testfifo") < 0)
450 		err(-1, "test_non_blocking_writer: unlink: testfifo");
451 }
452 
453 int
main(void)454 main(void)
455 {
456 
457 	if (geteuid() != 0)
458 		errx(-1, "must be run as root");
459 
460 	strcpy(temp_dir, "fifo_open.XXXXXXXXXXX");
461 	if (mkdtemp(temp_dir) == NULL)
462 		err(-1, "mkdtemp");
463 	if (chdir(temp_dir) < 0)
464 		err(-1, "chdir: %s", temp_dir);
465 	atexit(atexit_temp_dir);
466 
467 	test_non_blocking_reader();
468 	test_non_blocking_writer();
469 
470 	test_blocking_reader();
471 	test_blocking_writer();
472 
473 	return (0);
474 }
475