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 * Various tests for posix_spawn(3C). Currently this mostly focuses on
18 * functionality added in POSIX 2024 which relates to SETSID and changing
19 * directories.
20 */
21
22 #include <err.h>
23 #include <stdlib.h>
24 #include <spawn.h>
25 #include <stdio.h>
26 #include <stdbool.h>
27 #include <unistd.h>
28 #include <sys/sysmacros.h>
29 #include <sys/debug.h>
30 #include <wait.h>
31 #include <string.h>
32 #include <fcntl.h>
33 #include <limits.h>
34 #include <errno.h>
35 #include <libgen.h>
36 #include <inttypes.h>
37
38 #include "posix_spawn_common.h"
39
40 /*
41 * This isn't const so we can refer to it in the argv arrays.
42 */
43 static char *spawn_pwd = "/usr/bin/pwd";
44 static char spawn_child[PATH_MAX];
45
46 /*
47 * This is an arbitrary fd that we believe will be okay to use in fchdir tests
48 * to overwrite.
49 */
50 #define SPAWN_FD 23
51
52 typedef struct spawn_dir_test {
53 const char *sdt_desc;
54 bool sdt_pass;
55 const char *sdt_pwd;
56 const char *sdt_dirs[16];
57 } spawn_dir_test_t;
58
59 static const spawn_dir_test_t spawn_dir_tests[] = {
60 {
61 .sdt_desc = "no chdir",
62 .sdt_pass = true,
63 .sdt_pwd = "/var/tmp"
64 }, {
65 .sdt_desc = "absolute path: /etc",
66 .sdt_pass = true,
67 .sdt_pwd = "/etc",
68 .sdt_dirs = { "/etc" }
69 }, {
70 .sdt_desc = "multiple absolute paths (1)",
71 .sdt_pass = true,
72 .sdt_pwd = "/dev/net",
73 .sdt_dirs = { "/etc", "/dev/net" }
74 }, {
75 .sdt_desc = "multiple absolute paths (2)",
76 .sdt_pass = true,
77 .sdt_pwd = "/var/svc",
78 .sdt_dirs = { "/proc/self", "/var/svc" }
79 }, {
80 .sdt_desc = "single relative path (1)",
81 .sdt_pass = true,
82 .sdt_pwd = "/var/tmp",
83 .sdt_dirs = { "." },
84 }, {
85 .sdt_desc = "single relative path (2)",
86 .sdt_pass = true,
87 .sdt_pwd = "/var",
88 .sdt_dirs = { ".." },
89 }, {
90 .sdt_desc = "multiple relative paths (1)",
91 .sdt_pass = true,
92 .sdt_pwd = "/usr/lib/dtrace",
93 .sdt_dirs = { "..", "..", "usr", "lib", "dtrace" },
94 }, {
95 .sdt_desc = "multiple relative paths (2)",
96 .sdt_pass = true,
97 .sdt_pwd = "/var/tmp",
98 .sdt_dirs = { "..", "tmp" },
99 }, {
100 .sdt_desc = "mixing absolute and relative paths (1)",
101 .sdt_pass = true,
102 .sdt_pwd = "/usr/lib/fm/fmd",
103 .sdt_dirs = { "..", "/usr/lib/fm", "fmd" },
104 }, {
105 .sdt_desc = "mixing absolute and relative paths (2)",
106 .sdt_pass = true,
107 .sdt_pwd = "/usr/bin",
108 .sdt_dirs = { "/usr/lib/64", "..", "..", "bin" },
109 }, {
110 .sdt_desc = "mixing absolute and relative paths (3)",
111 .sdt_pass = true,
112 .sdt_pwd = "/etc/svc/volatile",
113 .sdt_dirs = { "/usr/lib/64", "..", "..", "bin",
114 "/etc/svc/volatile" },
115 }, {
116 /*
117 * Note, these bad path tests will not be terribly meaningful
118 * for fchdir because the open will fail.
119 */
120 .sdt_desc = "bad path 1",
121 .sdt_pass = false,
122 .sdt_dirs = { "/#error//?*!@#$!asdf/please/don't/exist" }
123 }, {
124 .sdt_desc = "bad path 2",
125 .sdt_pass = false,
126 .sdt_dirs = { "/tmp", "\x001\x002\x003\x004\x003\x042" }
127 }
128 };
129
130 typedef struct spawn_flags_test {
131 const char *sft_desc;
132 int sft_ret;
133 short sft_flags;
134 } spawn_flags_test_t;
135
136 static const spawn_flags_test_t spawn_flags_tests[] = {
137 {
138 .sft_desc = "no flags",
139 .sft_ret = 0,
140 .sft_flags = 0
141 }, {
142 .sft_desc = "flag SETPGROUP",
143 .sft_ret = 0,
144 .sft_flags = POSIX_SPAWN_SETPGROUP
145 }, {
146 .sft_desc = "flag SETSID",
147 .sft_ret = 0,
148 .sft_flags = POSIX_SPAWN_SETSID
149 }, {
150 .sft_desc = "flags SETSID | SETPGROUP",
151 .sft_ret = EPERM,
152 .sft_flags = POSIX_SPAWN_SETSID | POSIX_SPAWN_SETPGROUP
153 }
154 };
155
156
157 static bool
posix_spawn_test_one_dir(const spawn_dir_test_t * test,int pipes[2],posix_spawn_file_actions_t * acts,const char * desc)158 posix_spawn_test_one_dir(const spawn_dir_test_t *test, int pipes[2],
159 posix_spawn_file_actions_t *acts, const char *desc)
160 {
161 int ret;
162 bool bret = false;
163 char *const argv[2] = { spawn_pwd, NULL };
164 char *const envp[1] = { NULL };
165 pid_t pid;
166 siginfo_t sig;
167 char pwd[PATH_MAX];
168 ssize_t pwd_len;
169
170 if ((ret = posix_spawn(&pid, spawn_pwd, acts, NULL, argv, envp)) != 0) {
171 if (!test->sdt_pass) {
172 (void) printf("TEST PASSED: %s (%s): posix_spawn "
173 "failed as expected\n", test->sdt_desc, desc);
174 bret = true;
175 goto out;
176 } else {
177 warnx("TEST FAILED: %s posix_spawn() failed with %s, "
178 "but expected success", test->sdt_desc,
179 strerrorname_np(ret));
180 goto out;
181 }
182 }
183
184 if (waitid(P_PID, pid, &sig, WEXITED) != 0) {
185 err(EXIT_FAILURE, "INTERNAL TEST ERROR: %s: failed to wait on "
186 "pid %" _PRIdID ", but posix_spawn executed it",
187 test->sdt_desc, pid);
188 }
189
190 if (sig.si_code != CLD_EXITED) {
191 warnx("TEST FAILED: %s: child did not successfully exit: "
192 "foud si_code: %d", test->sdt_desc, sig.si_code);
193 goto out;
194 }
195
196 if (sig.si_status != 0) {
197 if (!test->sdt_pass) {
198 (void) printf("TEST PASSED: %s (%s): child process "
199 "failed", test->sdt_desc, desc);
200 bret = true;
201 goto out;
202 }
203
204 warnx("TEST FAILED: %s: child exited with status %d, expected "
205 "success", test->sdt_desc, sig.si_status);
206 goto out;
207 } else if (!test->sdt_pass) {
208 warnx("TEST FAILED: %s: child exited successfully, but "
209 "expected failure", test->sdt_desc);
210 goto out;
211 }
212
213 /*
214 * At this point we know that we have a pwd process that has
215 * successfully exited. We should be able to perform a non-blocking read
216 * from the pipe successfully and get its working directory. pwd(1)
217 * appends a new line. We remove it.
218 */
219 pwd[0] = 0;
220 pwd_len = read(pipes[0], pwd, sizeof (pwd));
221 if (pwd_len < 0) {
222 warn("TEST FAILED: %s: failed to read pwd from pipe",
223 test->sdt_desc);
224 goto out;
225 } else if (pwd_len == 0) {
226 warn("TEST FAILED: %s: got zero byte read from pipe?!",
227 test->sdt_desc);
228 goto out;
229 }
230 pwd[pwd_len - 1] = '\0';
231
232 if (strcmp(pwd, test->sdt_pwd) != 0) {
233 warnx("TEST FAILED: %s: found pwd '%s', expected '%s'",
234 test->sdt_desc, pwd, test->sdt_pwd);
235 goto out;
236 }
237
238 (void) printf("TEST PASSED: %s (%s)\n", test->sdt_desc, desc);
239
240 bret = true;
241 out:
242 return (bret);
243 }
244
245 static bool
posix_spawn_test_one_chdir(const spawn_dir_test_t * test)246 posix_spawn_test_one_chdir(const spawn_dir_test_t *test)
247 {
248 int ret, pipes[2];
249 bool bret = false;
250 posix_spawn_file_actions_t acts;
251
252 /*
253 * We set up a pipe to act as stdout so we can capture the output from
254 * pwd. While we could use /proc to try and do this, we prefer this
255 * mechanism.
256 */
257 posix_spawn_pipe_setup(&acts, pipes);
258
259 for (size_t i = 0; i < ARRAY_SIZE(test->sdt_dirs); i++) {
260 if (test->sdt_dirs[i] == NULL)
261 break;
262
263 ret = posix_spawn_file_actions_addchdir(&acts,
264 test->sdt_dirs[i]);
265 if (ret != 0) {
266 warnc(ret, "TEST FAILED: %s: adding path '%s' "
267 "(%zu) failed unexpectedly", test->sdt_desc,
268 test->sdt_dirs[i], i);
269 goto out;
270 }
271 }
272
273 bret = posix_spawn_test_one_dir(test, pipes, &acts, "chdir");
274 out:
275 VERIFY0(posix_spawn_file_actions_destroy(&acts));
276 VERIFY0(close(pipes[1]));
277 VERIFY0(close(pipes[0]));
278 return (bret);
279 }
280
281 static bool
posix_spawn_test_one_fchdir(const spawn_dir_test_t * test)282 posix_spawn_test_one_fchdir(const spawn_dir_test_t *test)
283 {
284 int ret, pipes[2];
285 bool bret = false;
286 posix_spawn_file_actions_t acts;
287
288 /*
289 * We set up a pipe to act as stdout so we can capture the output from
290 * pwd. While we could use /proc to try and do this, we prefer this
291 * mechanism.
292 */
293 posix_spawn_pipe_setup(&acts, pipes);
294
295 /*
296 * For the fchdir tests we go in a loop over these directories opening
297 * an fd, doing an fchdir to it, and then closing it.
298 */
299 for (size_t i = 0; i < ARRAY_SIZE(test->sdt_dirs); i++) {
300 if (test->sdt_dirs[i] == NULL)
301 break;
302
303 ret = posix_spawn_file_actions_addopen(&acts, SPAWN_FD,
304 test->sdt_dirs[i], O_RDONLY | O_DIRECTORY, 0);
305 if (ret != 0) {
306 warnc(ret, "TEST FAILED: %s: adding open action for "
307 "path '%s' (%zu) failed unexpectedly",
308 test->sdt_desc, test->sdt_dirs[i], i);
309 goto out;
310 }
311
312 ret = posix_spawn_file_actions_addfchdir(&acts, SPAWN_FD);
313 if (ret != 0) {
314 warnc(ret, "TEST FAILED: %s: adding fchdir action for "
315 "path '%s' (%zu) failed unexpectedly",
316 test->sdt_desc, test->sdt_dirs[i], i);
317 goto out;
318 }
319
320 ret = posix_spawn_file_actions_addclose(&acts, SPAWN_FD);
321 if (ret != 0) {
322 warnc(ret, "TEST FAILED: %s: adding close action for "
323 "path '%s' (%zu) failed unexpectedly",
324 test->sdt_desc, test->sdt_dirs[i], i);
325 goto out;
326 }
327 }
328
329 bret = posix_spawn_test_one_dir(test, pipes, &acts, "fchdir");
330 out:
331 VERIFY0(posix_spawn_file_actions_destroy(&acts));
332 VERIFY0(close(pipes[1]));
333 VERIFY0(close(pipes[0]));
334 return (bret);
335 }
336
337 /*
338 * Test a few different bad file actions.
339 */
340 static bool
posix_spawn_test_bad_actions(void)341 posix_spawn_test_bad_actions(void)
342 {
343 int ret;
344 bool bret = true;
345 posix_spawn_file_actions_t acts;
346
347 if ((ret = posix_spawn_file_actions_init(&acts)) != 0) {
348 errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to "
349 "initialize posix_spawn file actions");
350 }
351
352 if ((ret = posix_spawn_file_actions_addfchdir(&acts, -23)) == 0) {
353 warnx("TEST FAILED: addfchdir() with bad fd: expected EBADF, "
354 "but returned successfully");
355 bret = false;
356 } else if (ret != EBADF) {
357 warnx("TEST FAILED: addfchdir with bad fd: failed with %s, "
358 "but expected EBADF", strerrorname_np(ret));
359 bret = false;
360 } else {
361 (void) printf("TEST PASSED: addfchdir() with bad fd: correctly "
362 "got EBADF\n");
363 }
364
365 if ((ret = posix_spawn_file_actions_addopen(&acts, -23, "/dev/null",
366 O_RDONLY, 0)) == 0) {
367 warnx("TEST FAILED: addopen() with bad fd: expected EBADF, "
368 "but returned successfully");
369 bret = false;
370 } else if (ret != EBADF) {
371 warnx("TEST FAILED: addopen with bad fd: failed with %s, "
372 "but expected EBADF", strerrorname_np(ret));
373 bret = false;
374 } else {
375 (void) printf("TEST PASSED: addopen() with bad fd: correctly "
376 "got EBADF\n");
377 }
378
379 if ((ret = posix_spawn_file_actions_addclose(&acts, -23)) == 0) {
380 warnx("TEST FAILED: addclose() with bad fd: expected EBADF, "
381 "but returned successfully");
382 bret = false;
383 } else if (ret != EBADF) {
384 warnx("TEST FAILED: addclose with bad fd: failed with %s, "
385 "but expected EBADF", strerrorname_np(ret));
386 bret = false;
387 } else {
388 (void) printf("TEST PASSED: addclose() with bad fd: correctly "
389 "got EBADF\n");
390 }
391
392 VERIFY0(posix_spawn_file_actions_destroy(&acts));
393 return (bret);
394 }
395
396 /*
397 * Verify that if we try to do an fchdir to an invalid fd that everything fails.
398 */
399 static bool
posix_spawn_test_bad_fchdir(void)400 posix_spawn_test_bad_fchdir(void)
401 {
402 int ret, pipes[2];
403 bool bret = false;
404 posix_spawn_file_actions_t acts;
405 spawn_dir_test_t test;
406
407 (void) memset(&test, 0, sizeof (test));
408 test.sdt_desc = "fchdir to closed fd";
409 test.sdt_pass = false;
410 test.sdt_pwd = "/nope";
411
412 /*
413 * We set up a pipe to act as stdout so we can capture the output from
414 * pwd. While we could use /proc to try and do this, we prefer this
415 * mechanism.
416 */
417 posix_spawn_pipe_setup(&acts, pipes);
418
419 ret = posix_spawn_file_actions_addclose(&acts, SPAWN_FD);
420 if (ret != 0) {
421 warnc(ret, "TEST FAILED: %s: adding close action failed "
422 "unexpectedly", test.sdt_desc);
423 goto out;
424 }
425
426 ret = posix_spawn_file_actions_addfchdir(&acts, SPAWN_FD);
427 if (ret != 0) {
428 warnc(ret, "TEST FAILED: %s: adding close action failed "
429 "unexpectedly", test.sdt_desc);
430 goto out;
431 }
432
433 bret = posix_spawn_test_one_dir(&test, pipes, &acts, "fchdir");
434 out:
435 VERIFY0(posix_spawn_file_actions_destroy(&acts));
436 VERIFY0(close(pipes[1]));
437 VERIFY0(close(pipes[0]));
438 return (bret);
439 }
440
441
442 /*
443 * An fchdir file action that names a descriptor too large to fit in the
444 * descriptor table is accepted when it is added (the fd is non-negative) but
445 * must fail the spawn with EBADF when the action runs.
446 */
447 static bool
posix_spawn_test_fchdir_maxfd(void)448 posix_spawn_test_fchdir_maxfd(void)
449 {
450 int ret, pipes[2];
451 bool bret = false;
452 posix_spawn_file_actions_t acts;
453 char *const argv[2] = { spawn_pwd, NULL };
454 char *const envp[1] = { NULL };
455 siginfo_t sig;
456 pid_t pid;
457
458 posix_spawn_pipe_setup(&acts, pipes);
459
460 ret = posix_spawn_file_actions_addfchdir(&acts, INT32_MAX);
461 if (ret != 0) {
462 warnc(ret, "TEST FAILED: addfchdir(INT32_MAX) failed "
463 "unexpectedly when added");
464 goto out;
465 }
466
467 ret = posix_spawn(&pid, spawn_pwd, &acts, NULL, argv, envp);
468 if (ret == 0) {
469 warnx("TEST FAILED: fchdir(INT32_MAX): posix_spawn "
470 "unexpectedly succeeded");
471 (void) waitid(P_PID, pid, &sig, WEXITED);
472 } else if (ret != EBADF) {
473 warnx("TEST FAILED: fchdir(INT32_MAX): posix_spawn failed with "
474 "%s, expected EBADF", strerrorname_np(ret));
475 } else {
476 (void) printf("TEST PASSED: fchdir(INT32_MAX): posix_spawn "
477 "correctly failed with EBADF\n");
478 bret = true;
479 }
480
481 out:
482 VERIFY0(posix_spawn_file_actions_destroy(&acts));
483 VERIFY0(close(pipes[1]));
484 VERIFY0(close(pipes[0]));
485 return (bret);
486 }
487
488
489 static bool
posix_spawn_test_one_flags(const spawn_flags_test_t * test)490 posix_spawn_test_one_flags(const spawn_flags_test_t *test)
491 {
492 int ret, pipes[2];
493 bool bret = true;
494 char *const argv[3] = { spawn_child, "sidpgid", NULL };
495 char *const envp[1] = { NULL };
496 spawn_sidpgid_result_t res;
497 posix_spawn_file_actions_t acts;
498 posix_spawnattr_t attr;
499 short flags;
500 pid_t pid, exp_sid, exp_pgid;
501 siginfo_t sig;
502 ssize_t n;
503 const char *sid_desc, *pgid_desc;
504
505 posix_spawn_pipe_setup(&acts, pipes);
506
507 if ((ret = posix_spawnattr_init(&attr)) != 0) {
508 errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to "
509 "initialize posix_spawn attributes");
510 }
511
512 VERIFY0(posix_spawnattr_getflags(&attr, &flags));
513 if (flags != 0) {
514 warnx("TEST FAILED: %s: initial flags are not zero, found 0x%x",
515 test->sft_desc, flags);
516 bret = false;
517 }
518 VERIFY0(posix_spawnattr_setflags(&attr, test->sft_flags));
519 VERIFY0(posix_spawnattr_getflags(&attr, &flags));
520 if (flags != test->sft_flags) {
521 warnx("TEST FAILED: %s: flags are don't match what we set: "
522 "found 0x%x, expected 0x%x", test->sft_desc, flags,
523 test->sft_flags);
524 bret = false;
525 }
526
527 ret = posix_spawn(&pid, spawn_child, &acts, &attr, argv, envp);
528 if (ret != test->sft_ret) {
529 if (test->sft_ret == 0) {
530 warnx("TEST FAILED: %s posix_spawn() failed with %s, "
531 "but expected success", test->sft_desc,
532 strerrorname_np(ret));
533 } else {
534 warnx("TEST FAILED: %s posix_spawn() failed with %s, "
535 "but expected %s", test->sft_desc,
536 strerrorname_np(ret),
537 strerrorname_np(test->sft_ret));
538 }
539 bret = false;
540 goto out;
541 }
542
543 if (test->sft_ret != 0) {
544 (void) printf("TEST PASSED: %s: posix_spawn() failed correctly "
545 "with %s\n", test->sft_desc, strerrorname_np(ret));
546 goto out;
547 }
548
549 if (waitid(P_PID, pid, &sig, WEXITED) != 0) {
550 err(EXIT_FAILURE, "INTERNAL TEST ERROR: %s: failed to wait on "
551 "pid %" _PRIdID ", but posix_spawn executed it",
552 test->sft_desc, pid);
553 }
554
555 if (sig.si_code != CLD_EXITED) {
556 warnx("TEST FAILED: %s: child did not successfully exit: "
557 "foud si_code: %d", test->sft_desc, sig.si_code);
558 bret = false;
559 goto out;
560 }
561
562 if (sig.si_status != 0) {
563 warnx("TEST FAILED: %s: child exited with status %d, expected "
564 "success", test->sft_desc, sig.si_status);
565 bret = false;
566 goto out;
567 }
568
569 /*
570 * The child writes the results of getsid(2) and getpgid(2) as binary
571 * data to our pipe. We should be able to read all of this in one swoop.
572 */
573 n = read(pipes[0], &res, sizeof (res));
574 if (n != sizeof (res)) {
575 warnx("TEST FAILED: %s: short read from pipe (%zd)",
576 test->sft_desc, n);
577 bret = false;
578 goto out;
579 }
580
581 /*
582 * Now we need to check the various process group and session IDs. We
583 * expect the following values:
584 *
585 * If the SETSID flag was set then the session ID should match the
586 * child's pid. Otherwise it should match our value.
587 *
588 * If the SETSID or SETPGROUP flag was set then the process group ID
589 * should match the child's pid. Otherwise it should match our value.
590 */
591 if ((test->sft_flags & POSIX_SPAWN_SETSID) != 0) {
592 exp_sid = pid;
593 sid_desc = "child's ID";
594 } else {
595 exp_sid = getsid(0);
596 sid_desc = "test's SID";
597 }
598
599 if ((test->sft_flags & (POSIX_SPAWN_SETSID |
600 POSIX_SPAWN_SETPGROUP)) != 0) {
601 exp_pgid = pid;
602 pgid_desc = "child's ID";
603 } else {
604 exp_pgid = getpgid(0);
605 pgid_desc = "test's PGID";
606 }
607
608 if (res.sspr_sid != exp_sid) {
609 warnx("TEST FAILED: %s: session ID mismatch: expected 0x%"
610 _PRIxID " (%s), found 0x%" _PRIxID, test->sft_desc, exp_sid,
611 sid_desc, res.sspr_sid);
612 bret = false;
613 }
614
615 if (res.sspr_pgid != exp_pgid) {
616 warnx("TEST FAILED: %s: process group ID mismatch: expected "
617 "0x%" _PRIxID " (%s), found 0x%" _PRIxID, test->sft_desc,
618 exp_pgid, pgid_desc, res.sspr_pgid);
619 bret = false;
620 }
621
622 if (bret) {
623 (void) printf("TEST PASSED: %s\n", test->sft_desc);
624 }
625
626 out:
627 VERIFY0(posix_spawnattr_destroy(&attr));
628 VERIFY0(posix_spawn_file_actions_destroy(&acts));
629 VERIFY0(close(pipes[1]));
630 VERIFY0(close(pipes[0]));
631 return (bret);
632 }
633
634
635 int
main(void)636 main(void)
637 {
638 const char *helpers[] = { POSIX_SPAWN_CHILD_HELPERS };
639 int ret = EXIT_SUCCESS;
640
641 /*
642 * Because this test wants to rely on a known starting directory, we're
643 * going to chdir into /var/tmp at the start of this.
644 */
645 if (chdir("/var/tmp") != 0) {
646 err(EXIT_FAILURE, "INTERNAL TEST ERROR: failed to cd into "
647 "/var/tmp");
648 }
649
650 for (size_t i = 0; i < ARRAY_SIZE(spawn_dir_tests); i++) {
651 if (!posix_spawn_test_one_chdir(&spawn_dir_tests[i]))
652 ret = EXIT_FAILURE;
653
654 if (!posix_spawn_test_one_fchdir(&spawn_dir_tests[i]))
655 ret = EXIT_FAILURE;
656 }
657
658 if (!posix_spawn_test_bad_actions()) {
659 ret = EXIT_FAILURE;
660 }
661
662 if (!posix_spawn_test_bad_fchdir()) {
663 ret = EXIT_FAILURE;
664 }
665
666 if (!posix_spawn_test_fchdir_maxfd())
667 ret = EXIT_FAILURE;
668
669 for (size_t h = 0; h < ARRAY_SIZE(helpers); h++) {
670 posix_spawn_find_helper(spawn_child, sizeof (spawn_child),
671 helpers[h]);
672 (void) printf("--- child helper: %s ---\n", helpers[h]);
673
674 for (size_t i = 0; i < ARRAY_SIZE(spawn_flags_tests); i++) {
675 if (!posix_spawn_test_one_flags(&spawn_flags_tests[i]))
676 ret = EXIT_FAILURE;
677 }
678 }
679
680 if (ret == EXIT_SUCCESS) {
681 (void) printf("All tests passed successfully!\n");
682 }
683
684 return (ret);
685 }
686