xref: /illumos-gate/usr/src/test/libc-tests/tests/posix_spawn/posix_spawn.c (revision 3114379f81d5ab88054ea9e72c8874984ea8c263)
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 2025 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 /*
39  * This isn't const so we can refer to it in the argv arrays.
40  */
41 static char *spawn_pwd = "/usr/bin/pwd";
42 static char spawn_getsid[PATH_MAX];
43 
44 /*
45  * This is an arbitrary fd that we believe will be okay to use in fchdir tests
46  * to overwrite.
47  */
48 #define	SPAWN_FD	23
49 
50 typedef struct spawn_dir_test {
51 	const char *sdt_desc;
52 	bool sdt_pass;
53 	const char *sdt_pwd;
54 	const char *sdt_dirs[16];
55 } spawn_dir_test_t;
56 
57 static const spawn_dir_test_t spawn_dir_tests[] = {
58 	{
59 		.sdt_desc = "no chdir",
60 		.sdt_pass = true,
61 		.sdt_pwd = "/var/tmp"
62 	}, {
63 		.sdt_desc = "absolute path: /etc",
64 		.sdt_pass = true,
65 		.sdt_pwd = "/etc",
66 		.sdt_dirs = { "/etc" }
67 	}, {
68 		.sdt_desc = "multiple absolute paths (1)",
69 		.sdt_pass = true,
70 		.sdt_pwd = "/dev/net",
71 		.sdt_dirs = { "/etc", "/dev/net" }
72 	}, {
73 		.sdt_desc = "multiple absolute paths (2)",
74 		.sdt_pass = true,
75 		.sdt_pwd = "/var/svc",
76 		.sdt_dirs = { "/proc/self", "/var/svc" }
77 	}, {
78 		.sdt_desc = "single relative path (1)",
79 		.sdt_pass = true,
80 		.sdt_pwd = "/var/tmp",
81 		.sdt_dirs = { "." },
82 	}, {
83 		.sdt_desc = "single relative path (2)",
84 		.sdt_pass = true,
85 		.sdt_pwd = "/var",
86 		.sdt_dirs = { ".." },
87 	}, {
88 		.sdt_desc = "multiple relative paths (1)",
89 		.sdt_pass = true,
90 		.sdt_pwd = "/usr/lib/dtrace",
91 		.sdt_dirs = { "..", "..", "usr", "lib", "dtrace" },
92 	}, {
93 		.sdt_desc = "multiple relative paths (2)",
94 		.sdt_pass = true,
95 		.sdt_pwd = "/var/tmp",
96 		.sdt_dirs = { "..", "tmp" },
97 	}, {
98 		.sdt_desc = "mixing absolute and relative paths (1)",
99 		.sdt_pass = true,
100 		.sdt_pwd = "/usr/lib/fm/fmd",
101 		.sdt_dirs = { "..", "/usr/lib/fm", "fmd" },
102 	}, {
103 		.sdt_desc = "mixing absolute and relative paths (2)",
104 		.sdt_pass = true,
105 		.sdt_pwd = "/usr/bin",
106 		.sdt_dirs = { "/usr/lib/64", "..", "..", "bin" },
107 	}, {
108 		.sdt_desc = "mixing absolute and relative paths (3)",
109 		.sdt_pass = true,
110 		.sdt_pwd = "/etc/svc/volatile",
111 		.sdt_dirs = { "/usr/lib/64", "..", "..", "bin",
112 		    "/etc/svc/volatile" },
113 	}, {
114 		/*
115 		 * Note, these bad path tests will not be terribly meaningful
116 		 * for fchdir because the open will fail.
117 		 */
118 		.sdt_desc = "bad path 1",
119 		.sdt_pass = false,
120 		.sdt_dirs = { "/#error//?*!@#$!asdf/please/don't/exist" }
121 	}, {
122 		.sdt_desc = "bad path 2",
123 		.sdt_pass = false,
124 		.sdt_dirs = { "/tmp", "\x001\x002\x003\x004\x003\x042" }
125 	}
126 };
127 
128 typedef struct spawn_flags_test {
129 	const char *sft_desc;
130 	int sft_ret;
131 	short sft_flags;
132 } spawn_flags_test_t;
133 
134 static const spawn_flags_test_t spawn_flags_tests[] = {
135 	{
136 		.sft_desc = "no flags",
137 		.sft_ret = 0,
138 		.sft_flags = 0
139 	}, {
140 		.sft_desc = "flag SETPGROUP",
141 		.sft_ret = 0,
142 		.sft_flags = POSIX_SPAWN_SETPGROUP
143 	}, {
144 		.sft_desc = "flag SETSID",
145 		.sft_ret = 0,
146 		.sft_flags = POSIX_SPAWN_SETSID
147 	}, {
148 		.sft_desc = "flags SETSID | SETPGROUP",
149 		.sft_ret = EPERM,
150 		.sft_flags = POSIX_SPAWN_SETSID | POSIX_SPAWN_SETPGROUP
151 	}
152 };
153 
154 /*
155  * Add standard actions to capture stdout but nothing else.
156  */
157 static void
posix_spawn_setup_fds(posix_spawn_file_actions_t * acts,int pipes[2])158 posix_spawn_setup_fds(posix_spawn_file_actions_t *acts, int pipes[2])
159 {
160 	int ret;
161 
162 	if (pipe2(pipes, O_NONBLOCK) != 0) {
163 		err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to create a "
164 		    "pipe");
165 	}
166 
167 	VERIFY3S(pipes[0], >, STDERR_FILENO);
168 	VERIFY3S(pipes[1], >, STDERR_FILENO);
169 
170 	if ((ret = posix_spawn_file_actions_init(acts)) != 0) {
171 		errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to "
172 		    "initialize posix_spawn file actions");
173 	}
174 
175 	if ((ret = posix_spawn_file_actions_addopen(acts, STDIN_FILENO,
176 	    "/dev/null", O_RDONLY, 0)) != 0) {
177 		errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to add "
178 		    "/dev/null open action");
179 	}
180 
181 	if ((ret = posix_spawn_file_actions_adddup2(acts, STDIN_FILENO,
182 	    STDERR_FILENO)) != 0) {
183 		errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to add "
184 		    "stderr dup action");
185 	}
186 
187 	if ((ret = posix_spawn_file_actions_adddup2(acts, pipes[1],
188 	    STDOUT_FILENO)) != 0) {
189 		errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to add "
190 		    "stdout dup action");
191 	}
192 
193 	if ((ret = posix_spawn_file_actions_addclose(acts, pipes[0])) != 0) {
194 		errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to add "
195 		    "pipes[0] close action");
196 	}
197 
198 	if ((ret = posix_spawn_file_actions_addclose(acts, pipes[1])) != 0) {
199 		errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to add "
200 		    "pipes[1] close action");
201 	}
202 }
203 
204 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)205 posix_spawn_test_one_dir(const spawn_dir_test_t *test, int pipes[2],
206     posix_spawn_file_actions_t *acts, const char *desc)
207 {
208 	int ret;
209 	bool bret = false;
210 	char *const argv[2] = { spawn_pwd, NULL };
211 	char *const envp[1] = { NULL };
212 	pid_t pid;
213 	siginfo_t sig;
214 	char pwd[PATH_MAX];
215 	ssize_t pwd_len;
216 
217 	if ((ret = posix_spawn(&pid, spawn_pwd, acts, NULL, argv, envp)) != 0) {
218 		if (!test->sdt_pass) {
219 			(void) printf("TEST PASSED: %s (%s): posix_spawn "
220 			    "failed as expected\n", test->sdt_desc, desc);
221 			bret = true;
222 			goto out;
223 		} else {
224 			warnx("TEST FAILED: %s posix_spawn() failed with %s, "
225 			    "but expected success", test->sdt_desc,
226 			    strerrorname_np(ret));
227 			goto out;
228 		}
229 	}
230 
231 	if (waitid(P_PID, pid, &sig, WEXITED) != 0) {
232 		err(EXIT_FAILURE, "INTERNAL TEST ERROR: %s: failed to wait on "
233 		    "pid %" _PRIdID ", but posix_spawn executed it",
234 		    test->sdt_desc, pid);
235 	}
236 
237 	if (sig.si_code != CLD_EXITED) {
238 		warnx("TEST FAILED: %s: child did not successfully exit: "
239 		    "foud si_code: %d", test->sdt_desc, sig.si_code);
240 		goto out;
241 	}
242 
243 	if (sig.si_status != 0) {
244 		if (!test->sdt_pass) {
245 			(void) printf("TEST PASSED: %s (%s): child process "
246 			    "failed", test->sdt_desc, desc);
247 			bret = true;
248 			goto out;
249 		}
250 
251 		warnx("TEST FAILED: %s: child exited with status %d, expected "
252 		    "success", test->sdt_desc, sig.si_status);
253 		goto out;
254 	} else if (!test->sdt_pass) {
255 		warnx("TEST FAILED: %s: child exited successfully, but "
256 		    "expected failure", test->sdt_desc);
257 		goto out;
258 	}
259 
260 	/*
261 	 * At this point we know that we have a pwd process that has
262 	 * successfully exited. We should be able to perform a non-blocking read
263 	 * from the pipe successfully and get its working directory. pwd(1)
264 	 * appends a new line. We remove it.
265 	 */
266 	pwd[0] = 0;
267 	pwd_len = read(pipes[0], pwd, sizeof (pwd));
268 	if (pwd_len < 0) {
269 		warn("TEST FAILED: %s: failed to read pwd from pipe",
270 		    test->sdt_desc);
271 		goto out;
272 	} else if (pwd_len == 0) {
273 		warn("TEST FAILED: %s: got zero byte read from pipe?!",
274 		    test->sdt_desc);
275 		goto out;
276 	}
277 	pwd[pwd_len - 1] = '\0';
278 
279 	if (strcmp(pwd, test->sdt_pwd) != 0) {
280 		warnx("TEST FAILED: %s: found pwd '%s', expected '%s'",
281 		    test->sdt_desc, pwd, test->sdt_pwd);
282 		goto out;
283 	}
284 
285 	(void) printf("TEST PASSED: %s (%s)\n", test->sdt_desc, desc);
286 
287 	bret = true;
288 out:
289 	return (bret);
290 }
291 
292 static bool
posix_spawn_test_one_chdir(const spawn_dir_test_t * test)293 posix_spawn_test_one_chdir(const spawn_dir_test_t *test)
294 {
295 	int ret, pipes[2];
296 	bool bret = false;
297 	posix_spawn_file_actions_t acts;
298 
299 	/*
300 	 * We set up a pipe to act as stdout so we can capture the output from
301 	 * pwd. While we could use /proc to try and do this, we prefer this
302 	 * mechanism.
303 	 */
304 	posix_spawn_setup_fds(&acts, pipes);
305 
306 	for (size_t i = 0; i < ARRAY_SIZE(test->sdt_dirs); i++) {
307 		if (test->sdt_dirs[i] == NULL)
308 			break;
309 
310 		ret = posix_spawn_file_actions_addchdir(&acts,
311 		    test->sdt_dirs[i]);
312 		if (ret != 0) {
313 			warnc(ret, "TEST FAILED: %s: adding path '%s' "
314 			    "(%zu) failed unexpectedly", test->sdt_desc,
315 			    test->sdt_dirs[i], i);
316 			goto out;
317 		}
318 	}
319 
320 	bret = posix_spawn_test_one_dir(test, pipes, &acts, "chdir");
321 out:
322 	VERIFY0(posix_spawn_file_actions_destroy(&acts));
323 	VERIFY0(close(pipes[1]));
324 	VERIFY0(close(pipes[0]));
325 	return (bret);
326 }
327 
328 static bool
posix_spawn_test_one_fchdir(const spawn_dir_test_t * test)329 posix_spawn_test_one_fchdir(const spawn_dir_test_t *test)
330 {
331 	int ret, pipes[2];
332 	bool bret = false;
333 	posix_spawn_file_actions_t acts;
334 
335 	/*
336 	 * We set up a pipe to act as stdout so we can capture the output from
337 	 * pwd. While we could use /proc to try and do this, we prefer this
338 	 * mechanism.
339 	 */
340 	posix_spawn_setup_fds(&acts, pipes);
341 
342 	/*
343 	 * For the fchdir tests we go in a loop over these directories opening
344 	 * an fd, doing an fchdir to it, and then closing it.
345 	 */
346 	for (size_t i = 0; i < ARRAY_SIZE(test->sdt_dirs); i++) {
347 		if (test->sdt_dirs[i] == NULL)
348 			break;
349 
350 		ret = posix_spawn_file_actions_addopen(&acts, SPAWN_FD,
351 		    test->sdt_dirs[i], O_RDONLY | O_DIRECTORY, 0);
352 		if (ret != 0) {
353 			warnc(ret, "TEST FAILED: %s: adding open action for "
354 			    "path '%s' (%zu) failed unexpectedly",
355 			    test->sdt_desc, test->sdt_dirs[i], i);
356 			goto out;
357 		}
358 
359 		ret = posix_spawn_file_actions_addfchdir(&acts, SPAWN_FD);
360 		if (ret != 0) {
361 			warnc(ret, "TEST FAILED: %s: adding fchdir action for "
362 			    "path '%s' (%zu) failed unexpectedly",
363 			    test->sdt_desc, test->sdt_dirs[i], i);
364 			goto out;
365 		}
366 
367 		ret = posix_spawn_file_actions_addclose(&acts, SPAWN_FD);
368 		if (ret != 0) {
369 			warnc(ret, "TEST FAILED: %s: adding close action for "
370 			    "path '%s' (%zu) failed unexpectedly",
371 			    test->sdt_desc, test->sdt_dirs[i], i);
372 			goto out;
373 		}
374 	}
375 
376 	bret = posix_spawn_test_one_dir(test, pipes, &acts, "fchdir");
377 out:
378 	VERIFY0(posix_spawn_file_actions_destroy(&acts));
379 	VERIFY0(close(pipes[1]));
380 	VERIFY0(close(pipes[0]));
381 	return (bret);
382 }
383 
384 /*
385  * Test a few different bad file actions.
386  */
387 static bool
posix_spawn_test_bad_actions(void)388 posix_spawn_test_bad_actions(void)
389 {
390 	int ret;
391 	bool bret = true;
392 	posix_spawn_file_actions_t acts;
393 
394 	if ((ret = posix_spawn_file_actions_init(&acts)) != 0) {
395 		errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: failed to "
396 		    "initialize posix_spawn file actions");
397 	}
398 
399 	if ((ret = posix_spawn_file_actions_addfchdir(&acts, -23)) == 0) {
400 		warnx("TEST FAILED: addfchdir() with bad fd: expected EBADF, "
401 		    "but returned successfully");
402 		bret = false;
403 	} else if (ret != EBADF) {
404 		warnx("TEST FAILED: addfchdir with bad fd: failed with %s, "
405 		    "but expected EBADF", strerrorname_np(ret));
406 		bret = false;
407 	} else {
408 		(void) printf("TEST PASSED: addfchdir() with bad fd: correctly "
409 		    "got EBADF\n");
410 	}
411 
412 	if ((ret = posix_spawn_file_actions_addopen(&acts, -23, "/dev/null",
413 	    O_RDONLY, 0)) == 0) {
414 		warnx("TEST FAILED: addopen() with bad fd: expected EBADF, "
415 		    "but returned successfully");
416 		bret = false;
417 	} else if (ret != EBADF) {
418 		warnx("TEST FAILED: addopen with bad fd: failed with %s, "
419 		    "but expected EBADF", strerrorname_np(ret));
420 		bret = false;
421 	} else {
422 		(void) printf("TEST PASSED: addopen() with bad fd: correctly "
423 		    "got EBADF\n");
424 	}
425 
426 	if ((ret = posix_spawn_file_actions_addclose(&acts, -23)) == 0) {
427 		warnx("TEST FAILED: addclose() with bad fd: expected EBADF, "
428 		    "but returned successfully");
429 		bret = false;
430 	} else if (ret != EBADF) {
431 		warnx("TEST FAILED: addclose with bad fd: failed with %s, "
432 		    "but expected EBADF", strerrorname_np(ret));
433 		bret = false;
434 	} else {
435 		(void) printf("TEST PASSED: addclose() with bad fd: correctly "
436 		    "got EBADF\n");
437 	}
438 
439 	VERIFY0(posix_spawn_file_actions_destroy(&acts));
440 	return (bret);
441 }
442 
443 /*
444  * Verify that if we try to do an fchdir to an invalid fd that everything fails.
445  */
446 static bool
posix_spawn_test_bad_fchdir(void)447 posix_spawn_test_bad_fchdir(void)
448 {
449 	int ret, pipes[2];
450 	bool bret = false;
451 	posix_spawn_file_actions_t acts;
452 	spawn_dir_test_t test;
453 
454 	(void) memset(&test, 0, sizeof (test));
455 	test.sdt_desc = "fchdir to closed fd";
456 	test.sdt_pass = false;
457 	test.sdt_pwd = "/nope";
458 
459 	/*
460 	 * We set up a pipe to act as stdout so we can capture the output from
461 	 * pwd. While we could use /proc to try and do this, we prefer this
462 	 * mechanism.
463 	 */
464 	posix_spawn_setup_fds(&acts, pipes);
465 
466 	ret = posix_spawn_file_actions_addclose(&acts, SPAWN_FD);
467 	if (ret != 0) {
468 		warnc(ret, "TEST FAILED: %s: adding close action failed "
469 		    "unexpectedly", test.sdt_desc);
470 		goto out;
471 	}
472 
473 	ret = posix_spawn_file_actions_addfchdir(&acts, SPAWN_FD);
474 	if (ret != 0) {
475 		warnc(ret, "TEST FAILED: %s: adding close action failed "
476 		    "unexpectedly", test.sdt_desc);
477 		goto out;
478 	}
479 
480 	bret = posix_spawn_test_one_dir(&test, pipes, &acts, "fchdir");
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[2] = { spawn_getsid, NULL };
495 	char *const envp[1] = { NULL };
496 	pid_t buf[2];
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 buf_len;
503 	const char *sid_desc, *pgid_desc;
504 
505 	posix_spawn_setup_fds(&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_getsid, &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 getsid.64 process writes as binary data the results of getsid(2)
571 	 * and getpgid(2) to our pipe. We should be able to read all of this in
572 	 * one swoop.
573 	 */
574 	buf_len = read(pipes[0], buf, sizeof (buf));
575 	if (buf_len < 0) {
576 		warn("TEST FAILED: %s: failed to read IDs from pipe",
577 		    test->sft_desc);
578 		bret = false;
579 		goto out;
580 	} else if (buf_len == 0) {
581 		warn("TEST FAILED: %s: got zero byte read from pipe?!",
582 		    test->sft_desc);
583 		bret = false;
584 		goto out;
585 	}
586 
587 	/*
588 	 * Now we need to check the various process group and session IDs. We
589 	 * expect the following values:
590 	 *
591 	 * If the SETSID flag was set then the session ID should match the
592 	 * child's pid. Otherwise it should match our value.
593 	 *
594 	 * If the SETSID or SETPGROUP flag was set then the process group ID
595 	 * should match the child's pid. Otherwise it should match our value.
596 	 */
597 	if ((test->sft_flags & POSIX_SPAWN_SETSID) != 0) {
598 		exp_sid = pid;
599 		sid_desc = "child's ID";
600 	} else {
601 		exp_sid = getsid(0);
602 		sid_desc = "test's SID";
603 	}
604 
605 	if ((test->sft_flags & (POSIX_SPAWN_SETSID |
606 	    POSIX_SPAWN_SETPGROUP)) != 0) {
607 		exp_pgid = pid;
608 		pgid_desc = "child's ID";
609 	} else {
610 		exp_pgid = getpgid(0);
611 		pgid_desc = "test's PGID";
612 	}
613 
614 	if (buf[0] != exp_sid) {
615 		warnx("TEST FAILED: %s: session ID mismatch: expected 0x%"
616 		    _PRIxID " (%s), found 0x%" _PRIxID, test->sft_desc, exp_sid,
617 		    sid_desc, buf[0]);
618 		bret = false;
619 	}
620 
621 	if (buf[1] != exp_pgid) {
622 		warnx("TEST FAILED: %s: process group ID mismatch: expected "
623 		    "0x%" _PRIxID " (%s), found 0x%" _PRIxID, test->sft_desc,
624 		    exp_pgid, pgid_desc, buf[1]);
625 		bret = false;
626 	}
627 
628 	if (bret) {
629 		(void) printf("TEST PASSED: %s\n", test->sft_desc);
630 	}
631 
632 out:
633 	VERIFY0(posix_spawnattr_destroy(&attr));
634 	VERIFY0(posix_spawn_file_actions_destroy(&acts));
635 	VERIFY0(close(pipes[1]));
636 	VERIFY0(close(pipes[0]));
637 	return (bret);
638 }
639 
640 /*
641  * Set up paths that are dependent on where our binary is found.
642  */
643 static void
posix_spawn_test_paths(void)644 posix_spawn_test_paths(void)
645 {
646 	ssize_t ret;
647 	char origin[PATH_MAX];
648 
649 	ret = readlink("/proc/self/path/a.out", origin, PATH_MAX - 1);
650 	if (ret < 0) {
651 		err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to read "
652 		    "a.out path");
653 	}
654 
655 	origin[ret] = '\0';
656 	if (snprintf(spawn_getsid, sizeof (spawn_getsid), "%s/getsid.64",
657 	    dirname(origin)) >= sizeof (spawn_getsid)) {
658 		errx(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to assemble "
659 		    "getsid.64 path");
660 	}
661 
662 	if (access(spawn_getsid, X_OK) != 0) {
663 		err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to access %s",
664 		    spawn_getsid);
665 	}
666 }
667 
668 int
main(void)669 main(void)
670 {
671 	int ret = EXIT_SUCCESS;
672 
673 	/*
674 	 * Because this test wants to rely on a known starting directory, we're
675 	 * going to chdir into /var/tmp at the start of this.
676 	 */
677 	if (chdir("/var/tmp") != 0) {
678 		err(EXIT_FAILURE, "INTERNAL TEST ERROR: failed to cd into "
679 		    "/var/tmp");
680 	}
681 
682 	posix_spawn_test_paths();
683 
684 	for (size_t i = 0; i < ARRAY_SIZE(spawn_dir_tests); i++) {
685 		if (!posix_spawn_test_one_chdir(&spawn_dir_tests[i]))
686 			ret = EXIT_FAILURE;
687 
688 		if (!posix_spawn_test_one_fchdir(&spawn_dir_tests[i]))
689 			ret = EXIT_FAILURE;
690 	}
691 
692 	if (!posix_spawn_test_bad_actions()) {
693 		ret = EXIT_FAILURE;
694 	}
695 
696 	if (!posix_spawn_test_bad_fchdir()) {
697 		ret = EXIT_FAILURE;
698 	}
699 
700 	for (size_t i = 0; i < ARRAY_SIZE(spawn_flags_tests); i++) {
701 		if (!posix_spawn_test_one_flags(&spawn_flags_tests[i]))
702 			ret = EXIT_FAILURE;
703 	}
704 
705 	if (ret == EXIT_SUCCESS) {
706 		(void) printf("All tests passed successfully!\n");
707 	}
708 
709 	return (ret);
710 }
711