xref: /illumos-gate/usr/src/test/libc-tests/tests/posix_spawn/posix_spawn_chdir.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  * 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 static bool
posix_spawn_test_one_flags(const spawn_flags_test_t * test)443 posix_spawn_test_one_flags(const spawn_flags_test_t *test)
444 {
445 	int ret, pipes[2];
446 	bool bret = true;
447 	char *const argv[3] = { spawn_child, "sidpgid", NULL };
448 	char *const envp[1] = { NULL };
449 	spawn_sidpgid_result_t res;
450 	posix_spawn_file_actions_t acts;
451 	posix_spawnattr_t attr;
452 	short flags;
453 	pid_t pid, exp_sid, exp_pgid;
454 	siginfo_t sig;
455 	ssize_t n;
456 	const char *sid_desc, *pgid_desc;
457 
458 	posix_spawn_pipe_setup(&acts, pipes);
459 
460 	if ((ret = posix_spawnattr_init(&attr)) != 0) {
461 		errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to "
462 		    "initialize posix_spawn attributes");
463 	}
464 
465 	VERIFY0(posix_spawnattr_getflags(&attr, &flags));
466 	if (flags != 0) {
467 		warnx("TEST FAILED: %s: initial flags are not zero, found 0x%x",
468 		    test->sft_desc, flags);
469 		bret = false;
470 	}
471 	VERIFY0(posix_spawnattr_setflags(&attr, test->sft_flags));
472 	VERIFY0(posix_spawnattr_getflags(&attr, &flags));
473 	if (flags != test->sft_flags) {
474 		warnx("TEST FAILED: %s: flags are don't match what we set: "
475 		    "found 0x%x, expected 0x%x", test->sft_desc, flags,
476 		    test->sft_flags);
477 		bret = false;
478 	}
479 
480 	ret = posix_spawn(&pid, spawn_child, &acts, &attr, argv, envp);
481 	if (ret != test->sft_ret) {
482 		if (test->sft_ret == 0) {
483 			warnx("TEST FAILED: %s posix_spawn() failed with %s, "
484 			    "but expected success", test->sft_desc,
485 			    strerrorname_np(ret));
486 		} else {
487 			warnx("TEST FAILED: %s posix_spawn() failed with %s, "
488 			    "but expected %s", test->sft_desc,
489 			    strerrorname_np(ret),
490 			    strerrorname_np(test->sft_ret));
491 		}
492 		bret = false;
493 		goto out;
494 	}
495 
496 	if (test->sft_ret != 0) {
497 		(void) printf("TEST PASSED: %s: posix_spawn() failed correctly "
498 		    "with %s\n", test->sft_desc, strerrorname_np(ret));
499 		goto out;
500 	}
501 
502 	if (waitid(P_PID, pid, &sig, WEXITED) != 0) {
503 		err(EXIT_FAILURE, "INTERNAL TEST ERROR: %s: failed to wait on "
504 		    "pid %" _PRIdID ", but posix_spawn executed it",
505 		    test->sft_desc, pid);
506 	}
507 
508 	if (sig.si_code != CLD_EXITED) {
509 		warnx("TEST FAILED: %s: child did not successfully exit: "
510 		    "foud si_code: %d", test->sft_desc, sig.si_code);
511 		bret = false;
512 		goto out;
513 	}
514 
515 	if (sig.si_status != 0) {
516 		warnx("TEST FAILED: %s: child exited with status %d, expected "
517 		    "success", test->sft_desc, sig.si_status);
518 		bret = false;
519 		goto out;
520 	}
521 
522 	/*
523 	 * The child writes the results of getsid(2) and getpgid(2) as binary
524 	 * data to our pipe. We should be able to read all of this in one swoop.
525 	 */
526 	n = read(pipes[0], &res, sizeof (res));
527 	if (n != sizeof (res)) {
528 		warnx("TEST FAILED: %s: short read from pipe (%zd)",
529 		    test->sft_desc, n);
530 		bret = false;
531 		goto out;
532 	}
533 
534 	/*
535 	 * Now we need to check the various process group and session IDs. We
536 	 * expect the following values:
537 	 *
538 	 * If the SETSID flag was set then the session ID should match the
539 	 * child's pid. Otherwise it should match our value.
540 	 *
541 	 * If the SETSID or SETPGROUP flag was set then the process group ID
542 	 * should match the child's pid. Otherwise it should match our value.
543 	 */
544 	if ((test->sft_flags & POSIX_SPAWN_SETSID) != 0) {
545 		exp_sid = pid;
546 		sid_desc = "child's ID";
547 	} else {
548 		exp_sid = getsid(0);
549 		sid_desc = "test's SID";
550 	}
551 
552 	if ((test->sft_flags & (POSIX_SPAWN_SETSID |
553 	    POSIX_SPAWN_SETPGROUP)) != 0) {
554 		exp_pgid = pid;
555 		pgid_desc = "child's ID";
556 	} else {
557 		exp_pgid = getpgid(0);
558 		pgid_desc = "test's PGID";
559 	}
560 
561 	if (res.sspr_sid != exp_sid) {
562 		warnx("TEST FAILED: %s: session ID mismatch: expected 0x%"
563 		    _PRIxID " (%s), found 0x%" _PRIxID, test->sft_desc, exp_sid,
564 		    sid_desc, res.sspr_sid);
565 		bret = false;
566 	}
567 
568 	if (res.sspr_pgid != exp_pgid) {
569 		warnx("TEST FAILED: %s: process group ID mismatch: expected "
570 		    "0x%" _PRIxID " (%s), found 0x%" _PRIxID, test->sft_desc,
571 		    exp_pgid, pgid_desc, res.sspr_pgid);
572 		bret = false;
573 	}
574 
575 	if (bret) {
576 		(void) printf("TEST PASSED: %s\n", test->sft_desc);
577 	}
578 
579 out:
580 	VERIFY0(posix_spawnattr_destroy(&attr));
581 	VERIFY0(posix_spawn_file_actions_destroy(&acts));
582 	VERIFY0(close(pipes[1]));
583 	VERIFY0(close(pipes[0]));
584 	return (bret);
585 }
586 
587 
588 int
main(void)589 main(void)
590 {
591 	const char *helpers[] = { POSIX_SPAWN_CHILD_HELPERS };
592 	int ret = EXIT_SUCCESS;
593 
594 	/*
595 	 * Because this test wants to rely on a known starting directory, we're
596 	 * going to chdir into /var/tmp at the start of this.
597 	 */
598 	if (chdir("/var/tmp") != 0) {
599 		err(EXIT_FAILURE, "INTERNAL TEST ERROR: failed to cd into "
600 		    "/var/tmp");
601 	}
602 
603 	for (size_t i = 0; i < ARRAY_SIZE(spawn_dir_tests); i++) {
604 		if (!posix_spawn_test_one_chdir(&spawn_dir_tests[i]))
605 			ret = EXIT_FAILURE;
606 
607 		if (!posix_spawn_test_one_fchdir(&spawn_dir_tests[i]))
608 			ret = EXIT_FAILURE;
609 	}
610 
611 	if (!posix_spawn_test_bad_actions()) {
612 		ret = EXIT_FAILURE;
613 	}
614 
615 	if (!posix_spawn_test_bad_fchdir()) {
616 		ret = EXIT_FAILURE;
617 	}
618 
619 	for (size_t h = 0; h < ARRAY_SIZE(helpers); h++) {
620 		posix_spawn_find_helper(spawn_child, sizeof (spawn_child),
621 		    helpers[h]);
622 		(void) printf("--- child helper: %s ---\n", helpers[h]);
623 
624 		for (size_t i = 0; i < ARRAY_SIZE(spawn_flags_tests); i++) {
625 			if (!posix_spawn_test_one_flags(&spawn_flags_tests[i]))
626 				ret = EXIT_FAILURE;
627 		}
628 	}
629 
630 	if (ret == EXIT_SUCCESS) {
631 		(void) printf("All tests passed successfully!\n");
632 	}
633 
634 	return (ret);
635 }
636