Home | History | Annotate | Line # | Download | only in posix_spawn
      1 /* $NetBSD: t_spawn.c,v 1.12 2025/03/16 15:35:36 riastradh Exp $ */
      2 
      3 /*-
      4  * Copyright (c) 2012, 2021 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Charles Zhang <charles (at) NetBSD.org> and
      9  * Martin Husemann <martin (at) NetBSD.org>.
     10  *
     11  * Redistribution and use in source and binary forms, with or without
     12  * modification, are permitted provided that the following conditions
     13  * are met:
     14  * 1. Redistributions of source code must retain the above copyright
     15  *    notice, this list of conditions and the following disclaimer.
     16  * 2. Redistributions in binary form must reproduce the above copyright
     17  *    notice, this list of conditions and the following disclaimer in the
     18  *    documentation and/or other materials provided with the distribution.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     24  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     30  * POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 #include <sys/cdefs.h>
     34 __RCSID("$NetBSD: t_spawn.c,v 1.12 2025/03/16 15:35:36 riastradh Exp $");
     35 
     36 #include <sys/fcntl.h>
     37 #include <sys/stat.h>
     38 #include <sys/types.h>
     39 #include <sys/wait.h>
     40 
     41 #include <atf-c.h>
     42 #include <errno.h>
     43 #include <fcntl.h>
     44 #include <limits.h>
     45 #include <signal.h>
     46 #include <spawn.h>
     47 #include <stdio.h>
     48 #include <stdlib.h>
     49 #include <string.h>
     50 #include <time.h>
     51 #include <unistd.h>
     52 
     53 #include "fa_spawn_utils.h"
     54 #include "h_macros.h"
     55 
     56 static void check_success(const char *, const char *);
     57 
     58 ATF_TC(t_spawn_ls);
     59 ATF_TC_HEAD(t_spawn_ls, tc)
     60 {
     61 	atf_tc_set_md_var(tc, "descr",
     62 	    "Tests a simple posix_spawn executing /bin/ls");
     63 }
     64 ATF_TC_BODY(t_spawn_ls, tc)
     65 {
     66 	char * const args[] = { __UNCONST("ls"), __UNCONST("-la"), NULL };
     67 
     68 	RZ(posix_spawn(NULL, "/bin/ls", NULL, NULL, args, NULL));
     69 }
     70 
     71 ATF_TC(t_spawnp_ls);
     72 ATF_TC_HEAD(t_spawnp_ls, tc)
     73 {
     74 	atf_tc_set_md_var(tc, "descr",
     75 	    "Tests a simple posix_spawnp executing ls via $PATH");
     76 }
     77 ATF_TC_BODY(t_spawnp_ls, tc)
     78 {
     79 	char * const args[] = { __UNCONST("ls"), __UNCONST("-la"), NULL };
     80 
     81 	RZ(posix_spawnp(NULL, "ls", NULL, NULL, args, NULL));
     82 }
     83 
     84 static void
     85 spawn_error(const atf_tc_t *tc, const char *name, int error)
     86 {
     87 	char buf[PATH_MAX];
     88 	char * const args[] = { __UNCONST(name), NULL };
     89 	int err;
     90 
     91 	snprintf(buf, sizeof(buf), "%s/%s",
     92 	    atf_tc_get_config_var(tc, "srcdir"), name);
     93 	err = posix_spawn(NULL, buf, NULL, NULL, args, NULL);
     94 	ATF_REQUIRE_MSG(err == error, "expected error %d, "
     95 	    "got %d when spawning %s", error, err, buf);
     96 }
     97 
     98 ATF_TC(t_spawn_zero);
     99 ATF_TC_HEAD(t_spawn_zero, tc)
    100 {
    101 	atf_tc_set_md_var(tc, "descr",
    102 	    "posix_spawn an invalid binary");
    103 }
    104 ATF_TC_BODY(t_spawn_zero, tc)
    105 {
    106 	spawn_error(tc, "h_zero", ENOEXEC);
    107 }
    108 
    109 ATF_TC(t_spawn_missing);
    110 ATF_TC_HEAD(t_spawn_missing, tc)
    111 {
    112 	atf_tc_set_md_var(tc, "descr",
    113 	    "posix_spawn a nonexistent binary");
    114 }
    115 ATF_TC_BODY(t_spawn_missing, tc)
    116 {
    117 	spawn_error(tc, "h_nonexist", ENOENT);
    118 }
    119 
    120 ATF_TC(t_spawn_nonexec);
    121 ATF_TC_HEAD(t_spawn_nonexec, tc)
    122 {
    123 	atf_tc_set_md_var(tc, "descr",
    124 	    "posix_spawn a script with non existing interpreter");
    125 }
    126 ATF_TC_BODY(t_spawn_nonexec, tc)
    127 {
    128 	spawn_error(tc, "h_nonexec", ENOENT);
    129 }
    130 
    131 ATF_TC(t_spawn_child);
    132 ATF_TC_HEAD(t_spawn_child, tc)
    133 {
    134 	atf_tc_set_md_var(tc, "descr",
    135 	    "posix_spawn a child and get its return code");
    136 }
    137 
    138 ATF_TC_BODY(t_spawn_child, tc)
    139 {
    140 	char buf[PATH_MAX];
    141 	char rv[2] = { '0', '\0' };
    142 	char * const args0[] = { __UNCONST("h_spawn"), rv, NULL };
    143 	int rets[] = { 0, 1, 7 };
    144 	int status;
    145 	pid_t pid;
    146 
    147 	snprintf(buf, sizeof(buf), "%s/h_spawn",
    148 	    atf_tc_get_config_var(tc, "srcdir"));
    149 
    150 	for (size_t i = 0; i < __arraycount(rets); i++) {
    151 		rv[0] = rets[i] + '0';
    152 		RZ(posix_spawn(&pid, buf, NULL, NULL, args0, NULL));
    153 		ATF_REQUIRE_MSG(pid > 0, "pid=%lld", (long long)pid);
    154 		RL(waitpid(pid, &status, 0));
    155 		ATF_REQUIRE_MSG((WIFEXITED(status) &&
    156 			WEXITSTATUS(status) == rets[i]),
    157 		    "status=0x%x", status);
    158 	}
    159 }
    160 
    161 #define FILENAME "output"
    162 
    163 enum chdirop {
    164 	OP_CHDIR = 1,
    165 	OP_FCHDIR = 2,
    166 };
    167 
    168 static void
    169 check_success(const char *dir, const char *file)
    170 {
    171 	ssize_t bytes_read;
    172 	int fd;
    173 	size_t sizeof_file = (size_t)filesize(file);
    174 	size_t sizeof_str;
    175 	char *contents;
    176 
    177 	REQUIRE_LIBC(contents = malloc(sizeof_file), NULL);
    178 
    179 	RL(fd = open(file, O_RDONLY));
    180 
    181 	/*
    182 	 * file contains form feed i.e ASCII - 10 at the end.
    183 	 * Therefore sizeof_file - 1
    184 	 */
    185 	sizeof_str = strlen(dir);
    186 	ATF_CHECK_MSG(sizeof_str == sizeof_file - 1, "%zu (%s) != %zu (%s)",
    187 	    sizeof_str, dir, sizeof_file - 1, file);
    188 
    189 	bytes_read = read(fd, contents, sizeof_file - 1);
    190 	contents[sizeof_file - 1] = '\0';
    191 	ATF_REQUIRE_MSG(strcmp(dir, contents) == 0,
    192 	    "[%s] != [%s] Directories don't match", dir, contents);
    193 
    194 	RL(close(fd));
    195 
    196 	RL(unlink(file));
    197 	free(contents);
    198 
    199 	/* XXX not really required */
    200 	ATF_REQUIRE_MSG((size_t)bytes_read == sizeof_str,
    201 	    "bytes_read=%zu sizeof_str=%zu", bytes_read, sizeof_str);
    202 }
    203 
    204 static void
    205 spawn_chdir(const char *dirpath, const char *filepath, enum chdirop operation,
    206     int expected_error)
    207 {
    208 	int error, fd = -1, status;
    209 	char * const args[2] = { __UNCONST("pwd"), NULL };
    210 	pid_t pid;
    211 	posix_spawnattr_t attr, *attr_p;
    212 	posix_spawn_file_actions_t fa;
    213 
    214 	if (filepath)
    215 		empty_outfile(filepath);
    216 
    217 	RZ(posix_spawn_file_actions_init(&fa));
    218 
    219 	switch (operation) {
    220 	case OP_CHDIR:
    221 		RZ(posix_spawn_file_actions_addchdir(&fa, dirpath));
    222 		break;
    223 
    224 	case OP_FCHDIR:
    225 		RL(fd = open(dirpath, O_RDONLY));
    226 		RZ(posix_spawn_file_actions_addfchdir(&fa, fd));
    227 		break;
    228 	}
    229 
    230 	/*
    231 	 * if POSIX_SPAWN_RETURNERROR is expected, then no need to open the
    232 	 * file
    233 	 */
    234 	if (expected_error == 0) {
    235 		RZ(posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO,
    236 		    FILENAME, O_WRONLY, 0));
    237 		attr_p = NULL;
    238 	} else {
    239 		RZ(posix_spawnattr_init(&attr));
    240 
    241 		/*
    242 		 * POSIX_SPAWN_RETURNERROR is a NetBSD specific flag that
    243 		 * will cause a "proper" return value from posix_spawn(2)
    244 		 * instead of a (potential) success there and a 127 exit
    245 		 * status from the child process (c.f. the non-diag variant
    246 		 * of this test).
    247 		 */
    248 		RZ(posix_spawnattr_setflags(&attr, POSIX_SPAWN_RETURNERROR));
    249 		attr_p = &attr;
    250 	}
    251 
    252 	error = posix_spawn(&pid, "/bin/pwd", &fa, attr_p, args, NULL);
    253 	ATF_REQUIRE_MSG(error == expected_error, "error=%d expected_error=%d",
    254 	    error, expected_error);
    255 
    256 	/* wait for the child to finish only when no spawnattr */
    257 	if (attr_p) {
    258 		RZ(posix_spawnattr_destroy(&attr));
    259 	} else {
    260 		RL(waitpid(pid, &status, 0));
    261 		ATF_REQUIRE_MSG((WIFEXITED(status) &&
    262 			WEXITSTATUS(status) == EXIT_SUCCESS),
    263 		    "[f]chdir failed");
    264 	}
    265 
    266 	RZ(posix_spawn_file_actions_destroy(&fa));
    267 
    268 	/*
    269 	 * The file incase of fchdir(),
    270 	 * should be closed before reopening in 'check_success'
    271 	 */
    272 	if (fd != -1) {
    273 		RL(close(fd));
    274 	}
    275 }
    276 
    277 ATF_TC(t_spawn_chdir_abs);
    278 ATF_TC_HEAD(t_spawn_chdir_abs, tc)
    279 {
    280 	atf_tc_set_md_var(tc, "descr",
    281 	    "Test posix_spawn_fa_addchdir for absolute path");
    282 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    283 }
    284 ATF_TC_BODY(t_spawn_chdir_abs, tc)
    285 {
    286 	char chdirpath[PATH_MAX], filepath[PATH_MAX];
    287 
    288 	REQUIRE_LIBC(getcwd(chdirpath, sizeof(chdirpath)), NULL);
    289 	RL(chdir("/"));
    290 	if (snprintf(filepath, sizeof(filepath), "%s/%s", chdirpath, FILENAME)
    291 	    >= (int)sizeof(filepath))
    292 		atf_tc_fail("too deep: %s", chdirpath);
    293 
    294 	spawn_chdir(chdirpath, filepath, OP_CHDIR, 0);
    295 
    296 	/* finally cross check the output of "pwd" directory */
    297 	check_success(chdirpath, filepath);
    298 }
    299 
    300 ATF_TC(t_spawn_chdir_rel);
    301 
    302 ATF_TC_HEAD(t_spawn_chdir_rel, tc)
    303 {
    304 	atf_tc_set_md_var(tc, "descr",
    305 	    "Test posix_spawn_fa_addchdir for relative path");
    306 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    307 }
    308 
    309 ATF_TC_BODY(t_spawn_chdir_rel, tc)
    310 {
    311 	const char *relative_dir = "ch-dir";
    312 	const char *testdir = getcwd(NULL, 0);
    313 	char *chdirwd, *filepath;
    314 
    315 	RL(mkdir(relative_dir, 0777));
    316 	RL(asprintf(&chdirwd, "%s/%s", testdir, relative_dir));
    317 	RL(asprintf(&filepath, "%s/%s", chdirwd, FILENAME));
    318 
    319 #ifdef DEBUG
    320 	printf("cwd: %s\n", testdir);
    321 	printf("chdirwd: %s\n", chdirwd);
    322 	printf("filepath: %s\n", filepath);
    323 #endif
    324 
    325 	spawn_chdir(relative_dir, filepath, 1, 0);
    326 
    327 	/* finally cross check the directory */
    328 	check_success(chdirwd, filepath);
    329 	free(chdirwd);
    330 	free(filepath);
    331 }
    332 
    333 ATF_TC(t_spawn_chdir_file);
    334 ATF_TC_HEAD(t_spawn_chdir_file, tc)
    335 {
    336 	atf_tc_set_md_var(tc, "descr",
    337 	    "Test posix_spawn_fa_addchdir on plain file (not a directory)");
    338 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    339 }
    340 ATF_TC_BODY(t_spawn_chdir_file, tc)
    341 {
    342 	char cwd[PATH_MAX], filepath[PATH_MAX];
    343 
    344 	REQUIRE_LIBC(getcwd(cwd, sizeof(cwd)), NULL);
    345 	if (snprintf(filepath, sizeof(filepath), "%s/%s", cwd, FILENAME)
    346 	    >= (int)sizeof(filepath))
    347 		atf_tc_fail("too deep: %s", cwd);
    348 
    349 	spawn_chdir(filepath, filepath, 1, ENOTDIR);
    350 }
    351 
    352 ATF_TC(t_spawn_chdir_invalid);
    353 ATF_TC_HEAD(t_spawn_chdir_invalid, tc)
    354 {
    355 	atf_tc_set_md_var(tc, "descr",
    356 	    "Test posix_spawn_fa_addchdir for an invalid dir");
    357 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    358 }
    359 ATF_TC_BODY(t_spawn_chdir_invalid, tc)
    360 {
    361 	spawn_chdir("/not/a/valid/dir", NULL, 1, ENOENT);
    362 }
    363 
    364 ATF_TC(t_spawn_chdir_permissions);
    365 ATF_TC_HEAD(t_spawn_chdir_permissions, tc)
    366 {
    367 	atf_tc_set_md_var(tc, "descr",
    368 	    "Test posix_spawn_file_actions_addchdir for prohibited directory");
    369 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    370 	atf_tc_set_md_var(tc, "require.user", "unprivileged");
    371 }
    372 ATF_TC_BODY(t_spawn_chdir_permissions, tc)
    373 {
    374 	const char *restricted_dir = "prohibited";
    375 
    376 	RL(mkdir(restricted_dir, 0055));
    377 
    378 	spawn_chdir(restricted_dir, NULL, 1, EACCES);
    379 }
    380 
    381 ATF_TC(t_spawn_fchdir_abs);
    382 ATF_TC_HEAD(t_spawn_fchdir_abs, tc)
    383 {
    384 	atf_tc_set_md_var(tc, "descr", "Test posix_spawn_fa_fchdir");
    385 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    386 }
    387 ATF_TC_BODY(t_spawn_fchdir_abs, tc)
    388 {
    389 	char chdirpath[PATH_MAX], filepath[PATH_MAX];
    390 
    391 	REQUIRE_LIBC(getcwd(chdirpath, sizeof(chdirpath)), NULL);
    392 	RL(chdir("/"));
    393 	if (snprintf(filepath, sizeof(filepath), "%s/%s", chdirpath, FILENAME)
    394 	    >= (int)sizeof(filepath))
    395 		atf_tc_fail("too deep: %s", chdirpath);
    396 
    397 	spawn_chdir(chdirpath, filepath, OP_FCHDIR, 0);
    398 
    399 	/* finally cross check the directory */
    400 	check_success(chdirpath, filepath);
    401 }
    402 
    403 ATF_TC(t_spawn_fchdir_rel);
    404 ATF_TC_HEAD(t_spawn_fchdir_rel, tc)
    405 {
    406 	atf_tc_set_md_var(tc, "descr",
    407 	    "Testing posix_spawn_file_actions_addfchdir on a relative "
    408 	    "directory");
    409 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    410 }
    411 ATF_TC_BODY(t_spawn_fchdir_rel, tc)
    412 {
    413 	const char *relative_dir = "ch-dir";
    414 	const char *testdir = getcwd(NULL, 0);
    415 	char *chdirwd, *filepath;
    416 
    417 	RL(mkdir(relative_dir, 0755));
    418 
    419 	/*
    420 	 * This is done in parts purposely.
    421 	 * It enables the abs path of the relative dir
    422 	 * to be passed to 'check_success()' for comparing
    423 	 */
    424 	RL(asprintf(&chdirwd, "%s/%s", testdir, relative_dir));
    425 	RL(asprintf(&filepath, "%s/%s", chdirwd, FILENAME));
    426 
    427 	spawn_chdir(relative_dir, filepath, 2, 0);
    428 
    429 	/* finally cross check the directory */
    430 	check_success(chdirwd, filepath);
    431 	free(chdirwd);
    432 	free(filepath);
    433 }
    434 
    435 ATF_TC(t_spawn_fchdir_file);
    436 ATF_TC_HEAD(t_spawn_fchdir_file, tc)
    437 {
    438 	atf_tc_set_md_var(tc, "descr",
    439 	    "Testing posix_spawn_file_actions_addfchdir on a "
    440 	    "regular file (not a directory)");
    441 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    442 }
    443 ATF_TC_BODY(t_spawn_fchdir_file, tc)
    444 {
    445 	char cwd[PATH_MAX], filepath[PATH_MAX];
    446 	int fd;
    447 
    448 	REQUIRE_LIBC(getcwd(cwd, sizeof(cwd)), NULL);
    449 	if (snprintf(filepath, sizeof(filepath), "%s/%s", cwd, FILENAME)
    450 	    >= (int)sizeof(filepath))
    451 		atf_tc_fail("too deep: %s", cwd);
    452 	RL(fd = open(FILENAME, O_WRONLY | O_CREAT | O_TRUNC, 0644));
    453 	RL(close(fd));
    454 
    455 	spawn_chdir(filepath, NULL, 2, ENOTDIR);
    456 }
    457 
    458 ATF_TC(t_spawn_fchdir_neg_fd);
    459 ATF_TC_HEAD(t_spawn_fchdir_neg_fd, tc)
    460 {
    461 	atf_tc_set_md_var(tc, "descr",
    462 	    "Testing posix_spawn_file_actions_addfchdir on a negative file "
    463 	    "descriptor");
    464 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    465 }
    466 ATF_TC_BODY(t_spawn_fchdir_neg_fd, tc)
    467 {
    468 	int error, fd;
    469 	posix_spawn_file_actions_t fa;
    470 
    471 	fd = -1;
    472 
    473 	RZ(posix_spawn_file_actions_init(&fa));
    474 
    475 	error = posix_spawn_file_actions_addfchdir(&fa, fd);
    476 	ATF_REQUIRE_MSG(error == EBADF, "error=%d", error);
    477 
    478 	RZ(posix_spawn_file_actions_destroy(&fa));
    479 }
    480 
    481 ATF_TC(t_spawn_fchdir_closed);
    482 ATF_TC_HEAD(t_spawn_fchdir_closed, tc)
    483 {
    484 	atf_tc_set_md_var(tc, "descr",
    485 	    "Testing posix_spawn_file_actions_addfchdir for a closed fd");
    486 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    487 }
    488 ATF_TC_BODY(t_spawn_fchdir_closed, tc)
    489 {
    490 	int error, fd;
    491 	pid_t pid;
    492 	char * const args[2] = { __UNCONST("pwd"), NULL };
    493 	posix_spawnattr_t attr;
    494 	posix_spawn_file_actions_t fa;
    495 
    496 	fd = 3;
    497 	RZ(posix_spawnattr_init(&attr));
    498 	/*
    499 	 * POSIX_SPAWN_RETURNERROR is a NetBSD specific flag that
    500 	 * will cause a "proper" return value from posix_spawn(2)
    501 	 * instead of a (potential) success there and a 127 exit
    502 	 * status from the child process (c.f. the non-diag variant
    503 	 * of this test).
    504 	 */
    505 	RZ(posix_spawnattr_setflags(&attr, POSIX_SPAWN_RETURNERROR));
    506 	RZ(posix_spawn_file_actions_init(&fa));
    507 	RZ(posix_spawn_file_actions_addfchdir(&fa, fd));
    508 
    509 	error = posix_spawn(&pid, "/bin/pwd", &fa, &attr, args, NULL);
    510 	ATF_REQUIRE_MSG(error == EBADF, "error=%d", error);
    511 
    512 	RZ(posix_spawn_file_actions_destroy(&fa));
    513 	RZ(posix_spawnattr_destroy(&attr));
    514 }
    515 
    516 ATF_TC(t_spawn_sig);
    517 ATF_TC_HEAD(t_spawn_sig, tc)
    518 {
    519 	atf_tc_set_md_var(tc, "descr",
    520 	    "Checks that posix_spawn does not drop pending signals");
    521 }
    522 ATF_TC_BODY(t_spawn_sig, tc)
    523 {
    524 	const char *srcdir = atf_tc_get_config_var(tc, "srcdir");
    525 	char h_execsig[PATH_MAX];
    526 	time_t start;
    527 
    528 	snprintf(h_execsig, sizeof(h_execsig), "%s/../h_execsig", srcdir);
    529 	REQUIRE_LIBC(signal(SIGPIPE, SIG_IGN), SIG_ERR);
    530 
    531 	for (start = time(NULL); time(NULL) - start <= 10;) {
    532 		int fd[2];
    533 		char *const argv[] = {h_execsig, NULL};
    534 		posix_spawn_file_actions_t fa;
    535 		pid_t pid;
    536 		int status;
    537 
    538 		RL(pipe2(fd, O_CLOEXEC));
    539 		RZ(posix_spawn_file_actions_init(&fa));
    540 		RZ(posix_spawn_file_actions_adddup2(&fa, fd[0], STDIN_FILENO));
    541 		RZ(posix_spawn(&pid, argv[0], &fa, NULL, argv, NULL));
    542 		RL(close(fd[0]));
    543 		RL(kill(pid, SIGTERM));
    544 		if (write(fd[1], (char[]){0}, 1) == -1 && errno != EPIPE)
    545 			atf_tc_fail("write failed: %s", strerror(errno));
    546 		RL(waitpid(pid, &status, 0));
    547 		ATF_REQUIRE_MSG(WIFSIGNALED(status),
    548 		    "child exited with status 0x%x", status);
    549 		ATF_REQUIRE_EQ_MSG(WTERMSIG(status), SIGTERM,
    550 		    "child exited on signal %d (%s)",
    551 		    WTERMSIG(status), strsignal(WTERMSIG(status)));
    552 		RL(close(fd[1]));
    553 		RZ(posix_spawn_file_actions_destroy(&fa));
    554 	}
    555 }
    556 
    557 ATF_TP_ADD_TCS(tp)
    558 {
    559 	ATF_TP_ADD_TC(tp, t_spawn_ls);
    560 	ATF_TP_ADD_TC(tp, t_spawnp_ls);
    561 	ATF_TP_ADD_TC(tp, t_spawn_zero);
    562 	ATF_TP_ADD_TC(tp, t_spawn_missing);
    563 	ATF_TP_ADD_TC(tp, t_spawn_nonexec);
    564 	ATF_TP_ADD_TC(tp, t_spawn_child);
    565 	ATF_TP_ADD_TC(tp, t_spawn_chdir_abs);
    566 	ATF_TP_ADD_TC(tp, t_spawn_chdir_rel);
    567 	ATF_TP_ADD_TC(tp, t_spawn_chdir_file);
    568 	ATF_TP_ADD_TC(tp, t_spawn_chdir_invalid);
    569 	ATF_TP_ADD_TC(tp, t_spawn_chdir_permissions);
    570 	ATF_TP_ADD_TC(tp, t_spawn_fchdir_abs);
    571 	ATF_TP_ADD_TC(tp, t_spawn_fchdir_rel);
    572 	ATF_TP_ADD_TC(tp, t_spawn_fchdir_file);
    573 	ATF_TP_ADD_TC(tp, t_spawn_fchdir_neg_fd);
    574 	ATF_TP_ADD_TC(tp, t_spawn_fchdir_closed);
    575 	ATF_TP_ADD_TC(tp, t_spawn_sig);
    576 
    577 	return atf_no_error();
    578 }
    579