Home | History | Annotate | Line # | Download | only in posix_spawn
t_spawn.c revision 1.9
      1 /* $NetBSD: t_spawn.c,v 1.9 2025/03/13 01:27:27 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.9 2025/03/13 01:27:27 riastradh Exp $");
     35 
     36 #include <atf-c.h>
     37 
     38 #include <sys/fcntl.h>
     39 #include <sys/stat.h>
     40 #include <sys/types.h>
     41 #include <sys/wait.h>
     42 
     43 #include <errno.h>
     44 #include <fcntl.h>
     45 #include <limits.h>
     46 #include <signal.h>
     47 #include <spawn.h>
     48 #include <stdarg.h>
     49 #include <stdio.h>
     50 #include <stdlib.h>
     51 #include <string.h>
     52 #include <time.h>
     53 #include <unistd.h>
     54 
     55 #include "fa_spawn_utils.h"
     56 #include "h_macros.h"
     57 
     58 static void check_success(const char *, int, ...);
     59 
     60 ATF_TC(t_spawn_ls);
     61 
     62 ATF_TC_HEAD(t_spawn_ls, tc)
     63 {
     64 	atf_tc_set_md_var(tc, "descr",
     65 	    "Tests a simple posix_spawn executing /bin/ls");
     66 }
     67 
     68 ATF_TC_BODY(t_spawn_ls, tc)
     69 {
     70 	char * const args[] = { __UNCONST("ls"), __UNCONST("-la"), NULL };
     71 	int err;
     72 
     73 	err = posix_spawn(NULL, "/bin/ls", NULL, NULL, args, NULL);
     74 	ATF_REQUIRE(err == 0);
     75 }
     76 
     77 ATF_TC(t_spawnp_ls);
     78 
     79 ATF_TC_HEAD(t_spawnp_ls, tc)
     80 {
     81 	atf_tc_set_md_var(tc, "descr",
     82 	    "Tests a simple posix_spawnp executing ls via $PATH");
     83 }
     84 
     85 ATF_TC_BODY(t_spawnp_ls, tc)
     86 {
     87 	char * const args[] = { __UNCONST("ls"), __UNCONST("-la"), NULL };
     88 	int err;
     89 
     90 	err = posix_spawnp(NULL, "ls", NULL, NULL, args, NULL);
     91 	ATF_REQUIRE(err == 0);
     92 }
     93 
     94 static void
     95 spawn_error(const atf_tc_t *tc, const char *name, int error)
     96 {
     97 	char buf[FILENAME_MAX];
     98 	char * const args[] = { __UNCONST(name), NULL };
     99 	int err;
    100 
    101 	snprintf(buf, sizeof buf, "%s/%s",
    102 	    atf_tc_get_config_var(tc, "srcdir"), name);
    103 	err = posix_spawn(NULL, buf, NULL, NULL, args, NULL);
    104 	ATF_REQUIRE_MSG(err == error, "expected error %d, "
    105 	    "got %d when spawning %s", error, err, buf);
    106 }
    107 
    108 ATF_TC(t_spawn_zero);
    109 
    110 ATF_TC_HEAD(t_spawn_zero, tc)
    111 {
    112 	atf_tc_set_md_var(tc, "descr",
    113 	    "posix_spawn an invalid binary");
    114 }
    115 
    116 ATF_TC_BODY(t_spawn_zero, tc)
    117 {
    118 	spawn_error(tc, "h_zero", ENOEXEC);
    119 }
    120 
    121 ATF_TC(t_spawn_missing);
    122 
    123 ATF_TC_HEAD(t_spawn_missing, tc)
    124 {
    125 	atf_tc_set_md_var(tc, "descr",
    126 	    "posix_spawn a non existant binary");
    127 }
    128 
    129 ATF_TC_BODY(t_spawn_missing, tc)
    130 {
    131 	spawn_error(tc, "h_nonexist", ENOENT);
    132 }
    133 
    134 ATF_TC(t_spawn_nonexec);
    135 
    136 ATF_TC_HEAD(t_spawn_nonexec, tc)
    137 {
    138 	atf_tc_set_md_var(tc, "descr",
    139 	    "posix_spawn a script with non existing interpreter");
    140 }
    141 
    142 ATF_TC_BODY(t_spawn_nonexec, tc)
    143 {
    144 	spawn_error(tc, "h_nonexec", ENOENT);
    145 }
    146 
    147 ATF_TC(t_spawn_child);
    148 
    149 ATF_TC_HEAD(t_spawn_child, tc)
    150 {
    151 	atf_tc_set_md_var(tc, "descr",
    152 	    "posix_spawn a child and get its return code");
    153 }
    154 
    155 ATF_TC_BODY(t_spawn_child, tc)
    156 {
    157 	char buf[FILENAME_MAX];
    158 	char rv[2] = { '0', '\0' };
    159 	char * const args0[] = { __UNCONST("h_spawn"), rv, NULL };
    160 	int rets[] = { 0, 1, 7 };
    161 	int err, status;
    162 	pid_t pid;
    163 
    164 	snprintf(buf, sizeof buf, "%s/h_spawn",
    165 	    atf_tc_get_config_var(tc, "srcdir"));
    166 
    167 	for (size_t i = 0; i < __arraycount(rets); i++) {
    168 		rv[0] = rets[i] + '0';
    169 		err = posix_spawn(&pid, buf, NULL, NULL, args0, NULL);
    170 		ATF_REQUIRE(err == 0);
    171 		ATF_REQUIRE(pid > 0);
    172 		waitpid(pid, &status, 0);
    173 		ATF_REQUIRE(WIFEXITED(status) &&
    174 		    WEXITSTATUS(status) == rets[i]);
    175 	}
    176 }
    177 
    178 #define CHDIRPATH "/tmp"
    179 #define FILENAME "output"
    180 #define FILEPATH "/tmp/output"
    181 
    182 #define CHDIR 1
    183 #define FCHDIR 2
    184 
    185 static void
    186 check_success(const char *file, int argc, ...)
    187 {
    188 	va_list ap;
    189 	ssize_t bytesRead;
    190 	int fd;
    191 	size_t sizeOfFile = (size_t)filesize(file);
    192 	size_t sizeOfStr;
    193 	char *contents;
    194 	const char *dir;
    195 
    196 	contents = malloc(sizeOfFile);
    197 	ATF_REQUIRE(contents != NULL);
    198 
    199 	/*
    200 	 * for now only 1 variadic argument expected
    201 	 * only from t_spawn_[f]chdir_rel
    202 	 */
    203 	if (argc != 0) {
    204 		va_start(ap, argc);
    205 		dir = va_arg(ap, char *);
    206 		ATF_REQUIRE(dir != NULL);
    207 		va_end(ap);
    208 	} else
    209 		dir = CHDIRPATH;
    210 
    211 	fd = open(file, O_RDONLY);
    212 	ATF_REQUIRE_MSG(fd != -1, "Can't open `%s' (%s)", file,
    213 	    strerror(errno));
    214 
    215 	/*
    216 	 * file contains form feed i.e ASCII - 10 at the end.
    217 	 * Therefore sizeOfFile - 1
    218 	 */
    219 	sizeOfStr = strlen(dir);
    220 	ATF_CHECK_MSG(sizeOfStr == sizeOfFile - 1, "%zu (%s) != %zu (%s)",
    221 	    sizeOfStr, dir, sizeOfFile - 1, file);
    222 
    223 	bytesRead = read(fd, contents, sizeOfFile - 1);
    224 	contents[sizeOfFile - 1] = '\0';
    225 	ATF_REQUIRE_MSG(strcmp(dir, contents) == 0,
    226 	    "[%s] != [%s] Directories dont match", dir, contents);
    227 
    228 	fd = close(fd);
    229 	ATF_REQUIRE(fd == 0);
    230 
    231 	unlink(file);
    232 	free(contents);
    233 
    234 	/* XXX not really required */
    235 	ATF_REQUIRE((size_t)bytesRead == sizeOfStr);
    236 }
    237 
    238 static void
    239 spawn_chdir(const char *dirpath, const char *filepath, int operation,
    240     int expected_error)
    241 {
    242 	int error, fd=-1, status;
    243 	char * const args[2] = { __UNCONST("pwd"), NULL };
    244 	pid_t pid;
    245 	posix_spawnattr_t attr, *attr_p;
    246 	posix_spawn_file_actions_t fa;
    247 
    248 	if (filepath)
    249 		empty_outfile(filepath);
    250 
    251 	error = posix_spawn_file_actions_init(&fa);
    252 	ATF_REQUIRE(error == 0);
    253 
    254 	switch(operation) {
    255 	case CHDIR:
    256 		error = posix_spawn_file_actions_addchdir(&fa, dirpath);
    257 		break;
    258 
    259 	case FCHDIR:
    260 		fd = open(dirpath, O_RDONLY);
    261 		ATF_REQUIRE(fd != -1);
    262 
    263 		error = posix_spawn_file_actions_addfchdir(&fa, fd);
    264 		break;
    265 	}
    266 	ATF_REQUIRE(error == 0);
    267 
    268 	/*
    269 	 * if POSIX_SPAWN_RETURNERROR is expected, then no need to open the
    270 	 * file
    271 	 */
    272 	if (expected_error == 0) {
    273 		error = posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO,
    274 		    FILENAME, O_WRONLY, 0);
    275 		ATF_REQUIRE(error == 0);
    276 		attr_p = NULL;
    277 	} else {
    278 		error = posix_spawnattr_init(&attr);
    279 		ATF_REQUIRE(error == 0);
    280 
    281 		/*
    282 		 * POSIX_SPAWN_RETURNERROR is a NetBSD specific flag that
    283 		 * will cause a "proper" return value from posix_spawn(2)
    284 		 * instead of a (potential) success there and a 127 exit
    285 		 * status from the child process (c.f. the non-diag variant
    286 		 * of this test).
    287 		 */
    288 		error = posix_spawnattr_setflags(&attr,
    289 		    POSIX_SPAWN_RETURNERROR);
    290 		ATF_REQUIRE(error == 0);
    291 		attr_p = &attr;
    292 	}
    293 
    294 	error = posix_spawn(&pid, "/bin/pwd", &fa, attr_p, args, NULL);
    295 	ATF_REQUIRE(error == expected_error);
    296 
    297 	/* wait for the child to finish only when no spawnattr */
    298 	if (attr_p) {
    299 		posix_spawnattr_destroy(&attr);
    300 	} else {
    301 		waitpid(pid, &status, 0);
    302 		ATF_REQUIRE_MSG(WIFEXITED(status) &&
    303 		    WEXITSTATUS(status) == EXIT_SUCCESS,
    304 		    "%s", "[f]chdir failed");
    305 	}
    306 
    307 	posix_spawn_file_actions_destroy(&fa);
    308 
    309 	/*
    310 	 * The file incase of fchdir(),
    311 	 * should be closed before reopening in 'check_success'
    312 	 */
    313 	if (fd != -1) {
    314 		error = close(fd);
    315 		ATF_REQUIRE(error == 0);
    316 	}
    317 }
    318 
    319 ATF_TC(t_spawn_chdir_abs);
    320 
    321 ATF_TC_HEAD(t_spawn_chdir_abs, tc)
    322 {
    323 	atf_tc_set_md_var(tc, "descr",
    324 	    "Test posix_spawn_fa_addchdir for absolute path");
    325 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    326 }
    327 
    328 ATF_TC_BODY(t_spawn_chdir_abs, tc)
    329 {
    330 	spawn_chdir(CHDIRPATH, FILEPATH, 1, 0);
    331 
    332 	/* finally cross check the output of "pwd" directory */
    333 	check_success(FILEPATH, 0);
    334 }
    335 
    336 ATF_TC(t_spawn_chdir_rel);
    337 
    338 ATF_TC_HEAD(t_spawn_chdir_rel, tc)
    339 {
    340 	atf_tc_set_md_var(tc, "descr",
    341 	    "Test posix_spawn_fa_addchdir for relative path");
    342 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    343 }
    344 
    345 
    346 ATF_TC_BODY(t_spawn_chdir_rel, tc)
    347 {
    348 	int error;
    349 	const char *relative_dir = "ch-dir";
    350 	const char *testdir = getcwd(NULL, 0);
    351 	char *chdirwd, *filepath;
    352 
    353 	/* cleanup previous */
    354 	error = asprintf(&filepath, "%s/%s", relative_dir, FILENAME);
    355 	ATF_CHECK(error != -1);
    356 	unlink(filepath);
    357 	free(filepath);
    358 
    359 	rmdir(relative_dir);
    360 	error = mkdir(relative_dir, 0777);
    361 	ATF_REQUIRE_MSG(error == 0, "mkdir `%s' (%s)", relative_dir,
    362 	    strerror(errno));
    363 
    364 	error = asprintf(&chdirwd, "%s/%s", testdir, relative_dir);
    365 	ATF_CHECK(error != -1);
    366 
    367 	error = asprintf(&filepath, "%s/%s", chdirwd, FILENAME);
    368 	ATF_CHECK(error != -1);
    369 
    370 #ifdef DEBUG
    371 	printf("cwd: %s\n", testdir);
    372 	printf("chdirwd: %s\n", chdirwd);
    373 	printf("filepath: %s\n", filepath);
    374 #endif
    375 
    376 	spawn_chdir(relative_dir, filepath, 1, 0);
    377 
    378 	/* finally cross check the directory */
    379 	check_success(filepath, 1, chdirwd);
    380 	free(chdirwd);
    381 	free(filepath);
    382 }
    383 
    384 ATF_TC(t_spawn_chdir_file);
    385 
    386 ATF_TC_HEAD(t_spawn_chdir_file, tc)
    387 {
    388 	atf_tc_set_md_var(tc, "descr",
    389 	    "Test posix_spawn_fa_addchdir on plain file (not a directory)");
    390 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    391 }
    392 
    393 ATF_TC_BODY(t_spawn_chdir_file, tc)
    394 {
    395 	spawn_chdir(FILEPATH, FILEPATH, 1, ENOTDIR);
    396 
    397 	unlink(FILEPATH);
    398 }
    399 
    400 ATF_TC(t_spawn_chdir_invalid);
    401 
    402 ATF_TC_HEAD(t_spawn_chdir_invalid, tc)
    403 {
    404 	atf_tc_set_md_var(tc, "descr",
    405 	    "Test posix_spawn_fa_addchdir for an invalid dir");
    406 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    407 }
    408 
    409 ATF_TC_BODY(t_spawn_chdir_invalid, tc)
    410 {
    411 	spawn_chdir("/not/a/valid/dir", NULL, 1, ENOENT);
    412 
    413 }
    414 
    415 ATF_TC(t_spawn_chdir_permissions);
    416 
    417 ATF_TC_HEAD(t_spawn_chdir_permissions, tc)
    418 {
    419 	atf_tc_set_md_var(tc, "descr",
    420 	    "Test posix_spawn_file_actions_addchdir for prohibited directory");
    421 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    422 	atf_tc_set_md_var(tc, "require.user", "unprivileged");
    423 }
    424 
    425 ATF_TC_BODY(t_spawn_chdir_permissions, tc)
    426 {
    427 	int error;
    428 	const char *restrRelDir = "prohibited";
    429 
    430 	rmdir(restrRelDir);
    431 	error = mkdir(restrRelDir, 0055);
    432 	ATF_REQUIRE(error == 0);
    433 
    434 	spawn_chdir(restrRelDir, NULL, 1, EACCES);
    435 }
    436 
    437 
    438 ATF_TC(t_spawn_fchdir_abs);
    439 
    440 ATF_TC_HEAD(t_spawn_fchdir_abs, tc)
    441 {
    442 	atf_tc_set_md_var(tc, "descr", "Test posix_spawn_fa_fchdir");
    443 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    444 }
    445 
    446 ATF_TC_BODY(t_spawn_fchdir_abs, tc)
    447 {
    448 	spawn_chdir(CHDIRPATH, FILEPATH, 2, 0);
    449 
    450 	/* finally cross check the directory */
    451 	check_success(FILEPATH, 0);
    452 }
    453 
    454 ATF_TC(t_spawn_fchdir_rel);
    455 
    456 ATF_TC_HEAD(t_spawn_fchdir_rel, tc)
    457 {
    458 	atf_tc_set_md_var(tc, "descr",
    459 	    "Testing posix_spawn_file_actions_addfchdir on a relative "
    460 	    "directory");
    461 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    462 }
    463 
    464 ATF_TC_BODY(t_spawn_fchdir_rel, tc)
    465 {
    466 	int error;
    467 	const char *relative_dir = "ch-dir";
    468 	const char *testdir = getcwd(NULL, 0);
    469 	char *chdirwd, *filepath;
    470 
    471 	rmdir(relative_dir);
    472 	error = mkdir(relative_dir, 0755);
    473 	ATF_REQUIRE(error == 0);
    474 
    475 	/*
    476 	 * This is done in parts purposely.
    477 	 * It enables the abs path of the relative dir
    478 	 * to be passed to 'check_success()' for comparing
    479 	 */
    480 	error = asprintf(&chdirwd, "%s/%s", testdir, relative_dir);
    481 	ATF_CHECK(error != -1);
    482 
    483 	error = asprintf(&filepath, "%s/%s", chdirwd, FILENAME);
    484 	ATF_CHECK(error != -1);
    485 
    486 	spawn_chdir(relative_dir, filepath, 2, 0);
    487 
    488 	/* finally cross check the directory */
    489 	check_success(filepath, 1, chdirwd);
    490 	free(chdirwd);
    491 	free(filepath);
    492 }
    493 
    494 ATF_TC(t_spawn_fchdir_file);
    495 
    496 ATF_TC_HEAD(t_spawn_fchdir_file, tc)
    497 {
    498 	atf_tc_set_md_var(tc, "descr",
    499 	    "Testing posix_spawn_file_actions_addfchdir on a "
    500 	    "regular file (not a directory)");
    501 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    502 }
    503 
    504 ATF_TC_BODY(t_spawn_fchdir_file, tc)
    505 {
    506 	int error, fd;
    507 
    508 	fd = open(FILEPATH, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    509 	ATF_REQUIRE_MSG(fd != -1, "Can't open `%s' (%s)", FILEPATH,
    510 	    strerror(errno));
    511 
    512 	error = close(fd);
    513 	ATF_REQUIRE(error == 0);
    514 
    515 	spawn_chdir(FILEPATH, NULL, 2, ENOTDIR);
    516 
    517 	unlink(FILEPATH);
    518 
    519 }
    520 
    521 ATF_TC(t_spawn_fchdir_neg_fd);
    522 
    523 ATF_TC_HEAD(t_spawn_fchdir_neg_fd, tc)
    524 {
    525 	atf_tc_set_md_var(tc, "descr",
    526 	    "Testing posix_spawn_file_actions_addfchdir on a negative file "
    527 	    "descriptor");
    528 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    529 }
    530 
    531 ATF_TC_BODY(t_spawn_fchdir_neg_fd, tc)
    532 {
    533 	int error, fd;
    534 	posix_spawn_file_actions_t fa;
    535 
    536 	fd = -1;
    537 
    538 	error = posix_spawn_file_actions_init(&fa);
    539 	ATF_REQUIRE(error == 0);
    540 
    541 	error = posix_spawn_file_actions_addfchdir(&fa, fd);
    542 	ATF_REQUIRE(error == EBADF);
    543 
    544 	posix_spawn_file_actions_destroy(&fa);
    545 }
    546 
    547 ATF_TC(t_spawn_fchdir_closed);
    548 
    549 ATF_TC_HEAD(t_spawn_fchdir_closed, tc)
    550 {
    551 	atf_tc_set_md_var(tc, "descr",
    552 	    "Testing posix_spawn_file_actions_addfchdir for a closed fd");
    553 	atf_tc_set_md_var(tc, "require.progs", "/bin/pwd");
    554 }
    555 
    556 ATF_TC_BODY(t_spawn_fchdir_closed, tc)
    557 {
    558 	int error, fd;
    559 	pid_t pid;
    560 	char * const args[2] = { __UNCONST("pwd"), NULL };
    561 	posix_spawnattr_t attr;
    562 	posix_spawn_file_actions_t fa;
    563 
    564 	fd = 3;
    565 	error = posix_spawnattr_init(&attr);
    566 	ATF_CHECK(error == 0);
    567 	/*
    568 	 * POSIX_SPAWN_RETURNERROR is a NetBSD specific flag that
    569 	 * will cause a "proper" return value from posix_spawn(2)
    570 	 * instead of a (potential) success there and a 127 exit
    571 	 * status from the child process (c.f. the non-diag variant
    572 	 * of this test).
    573 	 */
    574 	error = posix_spawnattr_setflags(&attr, POSIX_SPAWN_RETURNERROR);
    575 	ATF_REQUIRE(error == 0);
    576 
    577 	error = posix_spawn_file_actions_init(&fa);
    578 	ATF_REQUIRE(error == 0);
    579 
    580 	error = posix_spawn_file_actions_addfchdir(&fa, fd);
    581 	ATF_REQUIRE(error == 0);
    582 
    583 	error = posix_spawn(&pid, "/bin/pwd", &fa, &attr, args, NULL);
    584 	ATF_REQUIRE(error == EBADF);
    585 
    586 	posix_spawn_file_actions_destroy(&fa);
    587 	posix_spawnattr_destroy(&attr);
    588 }
    589 
    590 ATF_TC(t_spawn_sig);
    591 ATF_TC_HEAD(t_spawn_sig, tc)
    592 {
    593 	atf_tc_set_md_var(tc, "descr",
    594 	    "Checks that posix_spawn does not drop pending signals");
    595 }
    596 ATF_TC_BODY(t_spawn_sig, tc)
    597 {
    598 	const char *srcdir = atf_tc_get_config_var(tc, "srcdir");
    599 	char h_execsig[PATH_MAX];
    600 	time_t start;
    601 
    602 	snprintf(h_execsig, sizeof(h_execsig), "%s/../h_execsig", srcdir);
    603 	REQUIRE_LIBC(signal(SIGPIPE, SIG_IGN), SIG_ERR);
    604 
    605 	atf_tc_expect_fail("PR kern/580911: after fork/execve or posix_spawn,"
    606 	    " parent kill(child, SIGTERM) has race condition"
    607 	    " making it unreliable");
    608 
    609 	for (start = time(NULL); time(NULL) - start <= 10;) {
    610 		int fd[2];
    611 		char *const argv[] = {h_execsig, NULL};
    612 		pid_t pid;
    613 		int status;
    614 
    615 		RL(pipe(fd));
    616 		RZ(posix_spawn(&pid, argv[0], NULL, NULL, argv, NULL));
    617 		RL(close(fd[0]));
    618 		RL(kill(pid, SIGTERM));
    619 		if (write(fd[1], (char[]){0}, 1) == -1 && errno != EPIPE)
    620 			atf_tc_fail("write failed: %s", strerror(errno));
    621 		RL(waitpid(pid, &status, 0));
    622 		ATF_REQUIRE_MSG(WIFSIGNALED(status),
    623 		    "child exited with status 0x%x", status);
    624 		ATF_REQUIRE_EQ_MSG(WTERMSIG(status), SIGTERM,
    625 		    "child exited on signal %d (%s)",
    626 		    WTERMSIG(status), strsignal(WTERMSIG(status)));
    627 		RL(close(fd[1]));
    628 	}
    629 }
    630 
    631 #undef CHDIR
    632 #undef FCHDIR
    633 #undef CHDIRPATH
    634 #undef FILENAME
    635 #undef FILEPATH
    636 
    637 ATF_TP_ADD_TCS(tp)
    638 {
    639 	ATF_TP_ADD_TC(tp, t_spawn_ls);
    640 	ATF_TP_ADD_TC(tp, t_spawnp_ls);
    641 	ATF_TP_ADD_TC(tp, t_spawn_zero);
    642 	ATF_TP_ADD_TC(tp, t_spawn_missing);
    643 	ATF_TP_ADD_TC(tp, t_spawn_nonexec);
    644 	ATF_TP_ADD_TC(tp, t_spawn_child);
    645 	ATF_TP_ADD_TC(tp, t_spawn_chdir_abs);
    646 	ATF_TP_ADD_TC(tp, t_spawn_chdir_rel);
    647 	ATF_TP_ADD_TC(tp, t_spawn_chdir_file);
    648 	ATF_TP_ADD_TC(tp, t_spawn_chdir_invalid);
    649 	ATF_TP_ADD_TC(tp, t_spawn_chdir_permissions);
    650 	ATF_TP_ADD_TC(tp, t_spawn_fchdir_abs);
    651 	ATF_TP_ADD_TC(tp, t_spawn_fchdir_rel);
    652 	ATF_TP_ADD_TC(tp, t_spawn_fchdir_file);
    653 	ATF_TP_ADD_TC(tp, t_spawn_fchdir_neg_fd);
    654 	ATF_TP_ADD_TC(tp, t_spawn_fchdir_closed);
    655 	ATF_TP_ADD_TC(tp, t_spawn_sig);
    656 
    657 	return atf_no_error();
    658 }
    659