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