xref: /illumos-gate/usr/src/test/libc-tests/tests/posix_spawn/posix_spawn_fileactions.c (revision 93d6c51de00648a982a50fbecc433b5482953fc7)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2026 Oxide Computer Company
14  */
15 
16 /*
17  * Tests for posix_spawn file actions: addopen, addclose, adddup2, and
18  * addclosefrom_np. Also tests interactions between file actions and
19  * descriptors marked FD_CLOFORK or FD_CLOEXEC in the parent.
20  */
21 
22 #include <err.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <libgen.h>
26 #include <limits.h>
27 #include <spawn.h>
28 #include <stdbool.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <wait.h>
34 #include <sys/sysmacros.h>
35 #include <sys/debug.h>
36 
37 #include "posix_spawn_common.h"
38 
39 #define	SFT_MAXFDS	4
40 
41 typedef struct spawn_fa_test spawn_fa_test_t;
42 typedef bool (*action_setup_fn_t)(spawn_fa_test_t *,
43     posix_spawn_file_actions_t *);
44 typedef bool (*action_pre_fn_t)(spawn_fa_test_t *);
45 
46 struct spawn_fa_test {
47 	const char		*sft_name;
48 	action_setup_fn_t	sft_setup;
49 	size_t			sft_nfds;
50 	int			sft_fds[SFT_MAXFDS];
51 	spawn_fd_result_t	sft_expected[SFT_MAXFDS];
52 	int			sft_open_fd;
53 	const char		*sft_open_path;
54 	int			sft_open_flags;
55 	int			sft_close_fd;
56 	int			sft_dup2_from;
57 	int			sft_dup2_to;
58 	/*
59 	 * Optional parent-side setup, run before the file actions setup.
60 	 * This callback is also responsible for updating fields in the test
61 	 * when they refer to runtime-allocated descriptors.
62 	 */
63 	action_pre_fn_t		sft_pre;
64 	int			sft_parent_fd;
65 	/* If non-zero, posix_spawn(3C) is expected to fail with this errno */
66 	int			sft_expect_errno;
67 };
68 
69 static char posix_spawn_child_path[PATH_MAX];
70 
71 /*
72  * Read fd results from the pipe and verify against expected values.
73  */
74 static bool
spawn_verify_fds(const char * desc,int pipefd,const spawn_fd_result_t * expected,size_t count)75 spawn_verify_fds(const char *desc, int pipefd,
76     const spawn_fd_result_t *expected, size_t count)
77 {
78 	bool ret = true;
79 
80 	for (size_t i = 0; i < count; i++) {
81 		spawn_fd_result_t res;
82 		ssize_t n;
83 
84 		n = read(pipefd, &res, sizeof (res));
85 		if (n != sizeof (res)) {
86 			warnx("TEST FAILED: %s: "
87 			    "failed to read result %zu from pipe "
88 			    "(got %zd bytes)", desc, i, n);
89 			return (false);
90 		}
91 
92 		if (res.srf_fd != expected[i].srf_fd) {
93 			warnx("TEST FAILED: %s: "
94 			    "result %zu: expected fd %d, got %d",
95 			    desc, i, expected[i].srf_fd, res.srf_fd);
96 			ret = false;
97 			continue;
98 		}
99 
100 		if (res.srf_open != expected[i].srf_open) {
101 			warnx("TEST FAILED: %s: fd %d: expected %s, got %s",
102 			    desc, res.srf_fd,
103 			    expected[i].srf_open ? "open" : "closed",
104 			    res.srf_open ? "open" : "closed");
105 			ret = false;
106 			continue;
107 		}
108 
109 		if (expected[i].srf_open &&
110 		    res.srf_flags != expected[i].srf_flags) {
111 			warnx("TEST FAILED: %s: fd %d: expected flags 0x%x, "
112 			    "got 0x%x", desc, res.srf_fd,
113 			    expected[i].srf_flags, res.srf_flags);
114 			ret = false;
115 			continue;
116 		}
117 	}
118 
119 	return (ret);
120 }
121 
122 /*
123  * Common test runner. Optionally runs a parent-side pre-test callback,
124  * sets up a pipe, calls the test's setup callback to add test-specific
125  * file actions, spawns posix_spawn_child in "fds" mode, and verifies
126  * the results against the expected values in the test struct.
127  *
128  * If the test specifies sft_expect_errno, posix_spawn is expected to
129  * fail with that errno; in that case the helper is not run.
130  */
131 static bool
spawn_test_fds(spawn_fa_test_t * test)132 spawn_test_fds(spawn_fa_test_t *test)
133 {
134 	const char *desc = test->sft_name;
135 	int pipes[2];
136 	posix_spawn_file_actions_t acts;
137 	bool ret = false;
138 	char fd_strs[SFT_MAXFDS][12];
139 	char *argv[SFT_MAXFDS + 3];
140 	size_t ai = 0;
141 
142 	test->sft_parent_fd = -1;
143 
144 	if (test->sft_pre != NULL && !test->sft_pre(test))
145 		goto cleanup;
146 
147 	posix_spawn_pipe_setup(&acts, pipes);
148 
149 	if (test->sft_setup != NULL && !test->sft_setup(test, &acts)) {
150 		VERIFY0(posix_spawn_file_actions_destroy(&acts));
151 		VERIFY0(close(pipes[0]));
152 		VERIFY0(close(pipes[1]));
153 		goto cleanup;
154 	}
155 
156 	argv[ai++] = posix_spawn_child_path;
157 	argv[ai++] = "fds";
158 	for (size_t i = 0; i < test->sft_nfds; i++) {
159 		(void) snprintf(fd_strs[i], sizeof (fd_strs[i]), "%d",
160 		    test->sft_fds[i]);
161 		argv[ai++] = fd_strs[i];
162 	}
163 	argv[ai] = NULL;
164 
165 	if (test->sft_expect_errno != 0) {
166 		pid_t pid;
167 		int err;
168 
169 		err = posix_spawn(&pid, posix_spawn_child_path, &acts, NULL,
170 		    argv, NULL);
171 
172 		if (err == 0) {
173 			siginfo_t sig;
174 
175 			warnx("TEST FAILED: %s: "
176 			    "posix_spawn unexpectedly succeeded", desc);
177 			(void) waitid(P_PID, pid, &sig, WEXITED);
178 		} else if (err != test->sft_expect_errno) {
179 			warnx("TEST FAILED: %s: "
180 			    "posix_spawn returned %s, expected %s",
181 			    desc, strerrorname_np(err),
182 			    strerrorname_np(test->sft_expect_errno));
183 		} else {
184 			ret = true;
185 		}
186 	} else {
187 		ret = posix_spawn_run_child(desc, posix_spawn_child_path,
188 		    &acts, NULL, argv);
189 		if (ret) {
190 			ret = spawn_verify_fds(desc, pipes[0],
191 			    test->sft_expected, test->sft_nfds);
192 		}
193 	}
194 
195 	VERIFY0(posix_spawn_file_actions_destroy(&acts));
196 	VERIFY0(close(pipes[1]));
197 	VERIFY0(close(pipes[0]));
198 
199 cleanup:
200 	if (test->sft_parent_fd >= 0)
201 		(void) close(test->sft_parent_fd);
202 
203 	return (ret);
204 }
205 
206 /*
207  * Generic setup functions for simple single-action tests.
208  * Parameters are read from the test struct.
209  */
210 static bool
addopen_setup(spawn_fa_test_t * test,posix_spawn_file_actions_t * acts)211 addopen_setup(spawn_fa_test_t *test, posix_spawn_file_actions_t *acts)
212 {
213 	int ret = posix_spawn_file_actions_addopen(acts, test->sft_open_fd,
214 	    test->sft_open_path, test->sft_open_flags, 0);
215 	if (ret != 0) {
216 		warnx("TEST FAILED: %s: addopen failed with %s",
217 		    test->sft_name, strerrorname_np(ret));
218 		return (false);
219 	}
220 	return (true);
221 }
222 
223 static bool
addclose_setup(spawn_fa_test_t * test,posix_spawn_file_actions_t * acts)224 addclose_setup(spawn_fa_test_t *test, posix_spawn_file_actions_t *acts)
225 {
226 	int ret = posix_spawn_file_actions_addclose(acts, test->sft_close_fd);
227 	if (ret != 0) {
228 		warnx("TEST FAILED: %s: addclose failed with %s",
229 		    test->sft_name, strerrorname_np(ret));
230 		return (false);
231 	}
232 	return (true);
233 }
234 
235 static bool
adddup2_setup(spawn_fa_test_t * test,posix_spawn_file_actions_t * acts)236 adddup2_setup(spawn_fa_test_t *test, posix_spawn_file_actions_t *acts)
237 {
238 	int ret = posix_spawn_file_actions_adddup2(acts, test->sft_dup2_from,
239 	    test->sft_dup2_to);
240 	if (ret != 0) {
241 		warnx("TEST FAILED: %s: adddup2 failed with %s",
242 		    test->sft_name, strerrorname_np(ret));
243 		return (false);
244 	}
245 	return (true);
246 }
247 
248 /*
249  * Custom setup functions for multi-action tests.
250  */
251 
252 /*
253  * Open fd 10 then close it - tests that close acts on a previously
254  * opened fd.
255  */
256 static bool
close_action_fd_setup(spawn_fa_test_t * test,posix_spawn_file_actions_t * acts)257 close_action_fd_setup(spawn_fa_test_t *test,
258     posix_spawn_file_actions_t *acts)
259 {
260 	const char *desc = test->sft_name;
261 	int ret;
262 
263 	ret = posix_spawn_file_actions_addopen(acts, 10,
264 	    "/dev/null", O_RDONLY, 0);
265 	if (ret != 0) {
266 		warnx("TEST FAILED: %s: addopen failed with %s",
267 		    desc, strerrorname_np(ret));
268 		return (false);
269 	}
270 
271 	ret = posix_spawn_file_actions_addclose(acts, 10);
272 	if (ret != 0) {
273 		warnx("TEST FAILED: %s: addclose failed with %s",
274 		    desc, strerrorname_np(ret));
275 		return (false);
276 	}
277 
278 	return (true);
279 }
280 
281 /*
282  * Open fds 10-13, then closefrom(11).
283  */
284 static bool
closefrom_setup(spawn_fa_test_t * test,posix_spawn_file_actions_t * acts)285 closefrom_setup(spawn_fa_test_t *test, posix_spawn_file_actions_t *acts)
286 {
287 	const char *desc = test->sft_name;
288 	int ret;
289 
290 	for (int fd = 10; fd <= 13; fd++) {
291 		ret = posix_spawn_file_actions_addopen(acts, fd,
292 		    "/dev/null", O_RDONLY, 0);
293 		if (ret != 0) {
294 			warnx("TEST FAILED: %s: addopen(%d) failed with %s",
295 			    desc, fd, strerrorname_np(ret));
296 			return (false);
297 		}
298 	}
299 
300 	ret = posix_spawn_file_actions_addclosefrom_np(acts, 11);
301 	if (ret != 0) {
302 		warnx("TEST FAILED: %s: addclosefrom_np failed with %s",
303 		    desc, strerrorname_np(ret));
304 		return (false);
305 	}
306 
307 	return (true);
308 }
309 
310 /*
311  * closefrom(3) then open fd 10. Verifies file actions execute sequentially.
312  */
313 static bool
closefrom_then_open_setup(spawn_fa_test_t * test,posix_spawn_file_actions_t * acts)314 closefrom_then_open_setup(spawn_fa_test_t *test,
315     posix_spawn_file_actions_t *acts)
316 {
317 	const char *desc = test->sft_name;
318 	int ret;
319 
320 	ret = posix_spawn_file_actions_addclosefrom_np(acts, 3);
321 	if (ret != 0) {
322 		warnx("TEST FAILED: %s: addclosefrom_np failed with %s",
323 		    desc, strerrorname_np(ret));
324 		return (false);
325 	}
326 
327 	ret = posix_spawn_file_actions_addopen(acts, 10,
328 	    "/dev/null", O_RDONLY, 0);
329 	if (ret != 0) {
330 		warnx("TEST FAILED: %s: addopen failed with %s",
331 		    desc, strerrorname_np(ret));
332 		return (false);
333 	}
334 
335 	return (true);
336 }
337 
338 /*
339  * Open fd 10, dup2 10->20, close 10.
340  */
341 static bool
open_dup_close_setup(spawn_fa_test_t * test,posix_spawn_file_actions_t * acts)342 open_dup_close_setup(spawn_fa_test_t *test, posix_spawn_file_actions_t *acts)
343 {
344 	const char *desc = test->sft_name;
345 	int ret;
346 
347 	ret = posix_spawn_file_actions_addopen(acts, 10,
348 	    "/dev/null", O_RDONLY, 0);
349 	if (ret != 0) {
350 		warnx("TEST FAILED: %s: addopen failed with %s",
351 		    desc, strerrorname_np(ret));
352 		return (false);
353 	}
354 
355 	ret = posix_spawn_file_actions_adddup2(acts, 10, 20);
356 	if (ret != 0) {
357 		warnx("TEST FAILED: %s: adddup2 failed with %s",
358 		    desc, strerrorname_np(ret));
359 		return (false);
360 	}
361 
362 	ret = posix_spawn_file_actions_addclose(acts, 10);
363 	if (ret != 0) {
364 		warnx("TEST FAILED: %s: addclose failed with %s",
365 		    desc, strerrorname_np(ret));
366 		return (false);
367 	}
368 
369 	return (true);
370 }
371 
372 /*
373  * Multiple opens on different fds in sequence.
374  */
375 static bool
multi_open_setup(spawn_fa_test_t * test,posix_spawn_file_actions_t * acts)376 multi_open_setup(spawn_fa_test_t *test, posix_spawn_file_actions_t *acts)
377 {
378 	const char *desc = test->sft_name;
379 	int ret;
380 
381 	ret = posix_spawn_file_actions_addopen(acts, 10, "/dev/null",
382 	    O_RDONLY, 0);
383 	if (ret != 0) {
384 		warnx("TEST FAILED: %s: addopen(10) failed with %s",
385 		    desc, strerrorname_np(ret));
386 		return (false);
387 	}
388 
389 	ret = posix_spawn_file_actions_addopen(acts, 11, "/dev/null",
390 	    O_WRONLY, 0);
391 	if (ret != 0) {
392 		warnx("TEST FAILED: %s: addopen(11) failed with %s",
393 		    desc, strerrorname_np(ret));
394 		return (false);
395 	}
396 
397 	ret = posix_spawn_file_actions_addopen(acts, 12, "/dev/null",
398 	    O_RDWR, 0);
399 	if (ret != 0) {
400 		warnx("TEST FAILED: %s: addopen(12) failed with %s",
401 		    desc, strerrorname_np(ret));
402 		return (false);
403 	}
404 
405 	return (true);
406 }
407 
408 /*
409  * Pre-test helpers for tests that exercise FD_CLOFORK and FD_CLOEXEC.
410  *
411  * These open a descriptor in the parent (with the requested flags) before the
412  * file actions are added so that the descriptor can be referenced by the file
413  * actions and/or checked in the spawned child. The opened descriptors are
414  * closed by the framework once the test has run.
415  */
416 static bool
spawn_open_parent_fd(spawn_fa_test_t * test,int fdflags)417 spawn_open_parent_fd(spawn_fa_test_t *test, int fdflags)
418 {
419 	int fd = open("/dev/null", O_RDONLY);
420 
421 	if (fd < 0) {
422 		warnx("INTERNAL TEST ERROR: %s: open /dev/null failed: %s",
423 		    test->sft_name, strerrorname_np(errno));
424 		return (false);
425 	}
426 
427 	if (fdflags != 0 && fcntl(fd, F_SETFD, fdflags) != 0) {
428 		warnx("INTERNAL TEST ERROR: %s: F_SETFD failed: %s",
429 		    test->sft_name, strerrorname_np(errno));
430 		(void) close(fd);
431 		return (false);
432 	}
433 
434 	test->sft_parent_fd = fd;
435 	return (true);
436 }
437 
438 /*
439  * Configure the test to verify that an FD_CLOFORK descriptor in the parent
440  * is not present in the spawned child.
441  */
442 static bool
clofork_present_pre(spawn_fa_test_t * test)443 clofork_present_pre(spawn_fa_test_t *test)
444 {
445 	if (!spawn_open_parent_fd(test, FD_CLOFORK))
446 		return (false);
447 
448 	test->sft_fds[0] = test->sft_parent_fd;
449 	test->sft_expected[0].srf_fd = test->sft_parent_fd;
450 	return (true);
451 }
452 
453 /*
454  * Configure the test to verify that an FD_CLOEXEC descriptor in the parent
455  * is not present in the spawned child once the new program image has been
456  * loaded.
457  */
458 static bool
cloexec_present_pre(spawn_fa_test_t * test)459 cloexec_present_pre(spawn_fa_test_t *test)
460 {
461 	if (!spawn_open_parent_fd(test, FD_CLOEXEC))
462 		return (false);
463 
464 	test->sft_fds[0] = test->sft_parent_fd;
465 	test->sft_expected[0].srf_fd = test->sft_parent_fd;
466 	return (true);
467 }
468 
469 /*
470  * Open an FD_CLOFORK descriptor in the parent and configure the test's
471  * adddup2 setup to use it as the source. The descriptor is closed at fork
472  * time, so the file action is expected to fail with EBADF.
473  */
474 static bool
clofork_dup_action_pre(spawn_fa_test_t * test)475 clofork_dup_action_pre(spawn_fa_test_t *test)
476 {
477 	if (!spawn_open_parent_fd(test, FD_CLOFORK))
478 		return (false);
479 
480 	test->sft_dup2_from = test->sft_parent_fd;
481 	return (true);
482 }
483 
484 /*
485  * Open an FD_CLOEXEC descriptor in the parent and configure the test's
486  * adddup2 setup to use it as the source. The duplicate does not inherit
487  * FD_CLOEXEC and is expected to persist past exec.
488  */
489 static bool
cloexec_dup_action_pre(spawn_fa_test_t * test)490 cloexec_dup_action_pre(spawn_fa_test_t *test)
491 {
492 	if (!spawn_open_parent_fd(test, FD_CLOEXEC))
493 		return (false);
494 
495 	test->sft_dup2_from = test->sft_parent_fd;
496 	test->sft_fds[0] = test->sft_parent_fd;
497 	test->sft_expected[0].srf_fd = test->sft_parent_fd;
498 	return (true);
499 }
500 
501 static spawn_fa_test_t tests[] = {
502 	/* addopen tests */
503 	{ .sft_name = "addopen /dev/null O_RDONLY on fd 10",
504 	    .sft_setup = addopen_setup,
505 	    .sft_nfds = 1, .sft_fds = { 10 },
506 	    .sft_expected = {
507 		{ .srf_fd = 10, .srf_open = 1, .srf_flags = O_RDONLY } },
508 	    .sft_open_fd = 10, .sft_open_path = "/dev/null",
509 	    .sft_open_flags = O_RDONLY },
510 	{ .sft_name = "addopen /dev/null O_WRONLY on fd 11",
511 	    .sft_setup = addopen_setup,
512 	    .sft_nfds = 1, .sft_fds = { 11 },
513 	    .sft_expected = {
514 		{ .srf_fd = 11, .srf_open = 1, .srf_flags = O_WRONLY } },
515 	    .sft_open_fd = 11, .sft_open_path = "/dev/null",
516 	    .sft_open_flags = O_WRONLY },
517 	{ .sft_name = "addopen /dev/null O_RDWR on fd 12",
518 	    .sft_setup = addopen_setup,
519 	    .sft_nfds = 1, .sft_fds = { 12 },
520 	    .sft_expected = {
521 		{ .srf_fd = 12, .srf_open = 1, .srf_flags = O_RDWR } },
522 	    .sft_open_fd = 12, .sft_open_path = "/dev/null",
523 	    .sft_open_flags = O_RDWR },
524 	{ .sft_name = "addopen onto STDIN_FILENO (replace stdin)",
525 	    .sft_setup = addopen_setup,
526 	    .sft_nfds = 1, .sft_fds = { STDIN_FILENO },
527 	    .sft_expected = {
528 		{ .srf_fd = 0, .srf_open = 1, .srf_flags = O_RDWR } },
529 	    .sft_open_fd = STDIN_FILENO, .sft_open_path = "/dev/null",
530 	    .sft_open_flags = O_RDWR },
531 	{ .sft_name = "addopen /dev/null on high fd (50)",
532 	    .sft_setup = addopen_setup,
533 	    .sft_nfds = 1, .sft_fds = { 50 },
534 	    .sft_expected = {
535 		{ .srf_fd = 50, .srf_open = 1, .srf_flags = O_RDONLY } },
536 	    .sft_open_fd = 50, .sft_open_path = "/dev/null",
537 	    .sft_open_flags = O_RDONLY },
538 
539 	/* addclose tests */
540 	{ .sft_name = "addclose fd opened by prior action",
541 	    .sft_setup = close_action_fd_setup,
542 	    .sft_nfds = 1, .sft_fds = { 10 },
543 	    .sft_expected = {
544 		{ .srf_fd = 10, .srf_open = 0 } } },
545 	{ .sft_name = "addclose STDIN_FILENO",
546 	    .sft_setup = addclose_setup,
547 	    .sft_nfds = 1, .sft_fds = { STDIN_FILENO },
548 	    .sft_expected = {
549 		{ .srf_fd = 0, .srf_open = 0 } },
550 	    .sft_close_fd = STDIN_FILENO },
551 
552 	/* adddup2 tests */
553 	{ .sft_name = "adddup2 STDOUT to fd 20",
554 	    .sft_setup = adddup2_setup,
555 	    .sft_nfds = 1, .sft_fds = { 20 },
556 	    .sft_expected = {
557 		{ .srf_fd = 20, .srf_open = 1, .srf_flags = O_RDWR } },
558 	    .sft_dup2_from = STDOUT_FILENO, .sft_dup2_to = 20 },
559 	{ .sft_name = "adddup2 to self (fd 0 -> fd 0)",
560 	    .sft_setup = adddup2_setup,
561 	    .sft_nfds = 1, .sft_fds = { STDIN_FILENO },
562 	    .sft_expected = {
563 		{ .srf_fd = 0, .srf_open = 1, .srf_flags = O_RDONLY } },
564 	    .sft_dup2_from = STDIN_FILENO, .sft_dup2_to = STDIN_FILENO },
565 
566 	/* addclosefrom_np tests */
567 	{ .sft_name = "addclosefrom_np(11) with fds 10-13",
568 	    .sft_setup = closefrom_setup,
569 	    .sft_nfds = 4, .sft_fds = { 10, 11, 12, 13 },
570 	    .sft_expected = {
571 		{ .srf_fd = 10, .srf_open = 1, .srf_flags = O_RDONLY },
572 		{ .srf_fd = 11, .srf_open = 0 },
573 		{ .srf_fd = 12, .srf_open = 0 },
574 		{ .srf_fd = 13, .srf_open = 0 } } },
575 	{ .sft_name = "closefrom(3) then open fd 10",
576 	    .sft_setup = closefrom_then_open_setup,
577 	    .sft_nfds = 1, .sft_fds = { 10 },
578 	    .sft_expected = {
579 		{ .srf_fd = 10, .srf_open = 1, .srf_flags = O_RDONLY } } },
580 
581 	/* Composition tests */
582 	{ .sft_name = "open fd 10, dup2 10->20, close 10",
583 	    .sft_setup = open_dup_close_setup,
584 	    .sft_nfds = 2, .sft_fds = { 10, 20 },
585 	    .sft_expected = {
586 		{ .srf_fd = 10, .srf_open = 0 },
587 		{ .srf_fd = 20, .srf_open = 1, .srf_flags = O_RDONLY } } },
588 	{ .sft_name = "multiple opens on fds 10, 11, 12",
589 	    .sft_setup = multi_open_setup,
590 	    .sft_nfds = 3, .sft_fds = { 10, 11, 12 },
591 	    .sft_expected = {
592 		{ .srf_fd = 10, .srf_open = 1, .srf_flags = O_RDONLY },
593 		{ .srf_fd = 11, .srf_open = 1, .srf_flags = O_WRONLY },
594 		{ .srf_fd = 12, .srf_open = 1, .srf_flags = O_RDWR } } },
595 
596 	/* FD_CLOFORK / FD_CLOEXEC interaction tests */
597 	{ .sft_name = "FD_CLOFORK descriptor not inherited by child",
598 	    .sft_pre = clofork_present_pre,
599 	    .sft_nfds = 1, .sft_fds = { -1 },
600 	    .sft_expected = {
601 		{ .srf_fd = -1, .srf_open = 0 } } },
602 	{ .sft_name = "FD_CLOEXEC descriptor not inherited by child",
603 	    .sft_pre = cloexec_present_pre,
604 	    .sft_nfds = 1, .sft_fds = { -1 },
605 	    .sft_expected = {
606 		{ .srf_fd = -1, .srf_open = 0 } } },
607 	{ .sft_name = "adddup2 of FD_CLOFORK fd fails with EBADF",
608 	    .sft_pre = clofork_dup_action_pre,
609 	    .sft_setup = adddup2_setup,
610 	    .sft_dup2_from = -1, .sft_dup2_to = 20,
611 	    .sft_expect_errno = EBADF },
612 	{ .sft_name = "adddup2 of FD_CLOEXEC fd produces persistent fd",
613 	    .sft_pre = cloexec_dup_action_pre,
614 	    .sft_setup = adddup2_setup,
615 	    .sft_dup2_from = -1, .sft_dup2_to = 20,
616 	    .sft_nfds = 2, .sft_fds = { -1, 20 },
617 	    .sft_expected = {
618 		{ .srf_fd = -1, .srf_open = 0 },
619 		{ .srf_fd = 20, .srf_open = 1, .srf_flags = O_RDONLY } } },
620 };
621 
622 int
main(void)623 main(void)
624 {
625 	const char *helpers[] = { POSIX_SPAWN_CHILD_HELPERS };
626 	int ret = EXIT_SUCCESS;
627 
628 	for (size_t h = 0; h < ARRAY_SIZE(helpers); h++) {
629 		posix_spawn_find_helper(posix_spawn_child_path,
630 		    sizeof (posix_spawn_child_path), helpers[h]);
631 		(void) printf("--- child helper: %s ---\n", helpers[h]);
632 
633 		for (size_t i = 0; i < ARRAY_SIZE(tests); i++) {
634 			if (spawn_test_fds(&tests[i])) {
635 				(void) printf("TEST PASSED: %s\n",
636 				    tests[i].sft_name);
637 			} else {
638 				ret = EXIT_FAILURE;
639 			}
640 		}
641 	}
642 
643 	if (ret == EXIT_SUCCESS)
644 		(void) printf("All tests passed successfully!\n");
645 
646 	return (ret);
647 }
648