#include <err.h>
#include <stdlib.h>
#include <spawn.h>
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/sysmacros.h>
#include <sys/ccompile.h>
#include <sys/debug.h>
#include <wait.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <errno.h>
#include <libgen.h>
#include <signal.h>
#include "posix_spawn_common.h"
typedef struct spawn_attr_test {
const char *sat_name;
bool (*sat_func)(struct spawn_attr_test *);
short sat_flags;
} spawn_attr_test_t;
static char posix_spawn_child_path[PATH_MAX];
static bool
setsigdef_test(spawn_attr_test_t *test)
{
const char *desc = test->sat_name;
int pipes[2];
posix_spawn_file_actions_t acts;
posix_spawnattr_t attr;
sigset_t sigdef;
struct sigaction sa;
spawn_sig_result_t res[2];
ssize_t n;
bool bret = true;
char sig1_str[12], sig2_str[12];
char *argv[] = { posix_spawn_child_path, "sigs", sig1_str, sig2_str,
NULL };
(void) snprintf(sig1_str, sizeof (sig1_str), "%d", SIGUSR1);
(void) snprintf(sig2_str, sizeof (sig2_str), "%d", SIGUSR2);
(void) memset(&sa, 0, sizeof (sa));
sa.sa_handler = SIG_IGN;
VERIFY0(sigaction(SIGUSR1, &sa, NULL));
VERIFY0(sigaction(SIGUSR2, &sa, NULL));
posix_spawn_pipe_setup(&acts, pipes);
VERIFY0(posix_spawnattr_init(&attr));
VERIFY0(posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGDEF));
(void) sigemptyset(&sigdef);
(void) sigaddset(&sigdef, SIGUSR1);
(void) sigaddset(&sigdef, SIGUSR2);
VERIFY0(posix_spawnattr_setsigdefault(&attr, &sigdef));
if (!posix_spawn_run_child(desc, posix_spawn_child_path,
&acts, &attr, argv)) {
bret = false;
goto out;
}
n = read(pipes[0], res, sizeof (res));
if (n != sizeof (res)) {
warnx("TEST FAILED: %s: short read from pipe (%zd)", desc, n);
bret = false;
goto out;
}
if (res[0].ssr_disp != 0) {
warnx("TEST FAILED: %s: "
"SIGUSR1 disposition is %d, expected SIG_DFL (0)",
desc, res[0].ssr_disp);
bret = false;
}
if (res[1].ssr_disp != 0) {
warnx("TEST FAILED: %s: "
"SIGUSR2 disposition is %d, expected SIG_DFL (0)",
desc, res[1].ssr_disp);
bret = false;
}
out:
sa.sa_handler = SIG_DFL;
VERIFY0(sigaction(SIGUSR1, &sa, NULL));
VERIFY0(sigaction(SIGUSR2, &sa, NULL));
VERIFY0(posix_spawnattr_destroy(&attr));
VERIFY0(posix_spawn_file_actions_destroy(&acts));
VERIFY0(close(pipes[1]));
VERIFY0(close(pipes[0]));
return (bret);
}
static bool
setsigdef_empty_test(spawn_attr_test_t *test)
{
const char *desc = test->sat_name;
int pipes[2];
posix_spawn_file_actions_t acts;
posix_spawnattr_t attr;
sigset_t sigdef;
struct sigaction sa;
spawn_sig_result_t res;
ssize_t n;
bool bret = true;
char sig_str[12];
char *argv[] = { posix_spawn_child_path, "sigs", sig_str, NULL };
(void) snprintf(sig_str, sizeof (sig_str), "%d", SIGUSR1);
(void) memset(&sa, 0, sizeof (sa));
sa.sa_handler = SIG_IGN;
VERIFY0(sigaction(SIGUSR1, &sa, NULL));
posix_spawn_pipe_setup(&acts, pipes);
VERIFY0(posix_spawnattr_init(&attr));
VERIFY0(posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGDEF));
(void) sigemptyset(&sigdef);
VERIFY0(posix_spawnattr_setsigdefault(&attr, &sigdef));
if (!posix_spawn_run_child(desc, posix_spawn_child_path,
&acts, &attr, argv)) {
bret = false;
goto out;
}
n = read(pipes[0], &res, sizeof (res));
if (n != sizeof (res)) {
warnx("TEST FAILED: %s: short read from pipe (%zd)", desc, n);
bret = false;
goto out;
}
if (res.ssr_disp != 1) {
warnx("TEST FAILED: %s: "
"SIGUSR1 disposition is %d, expected SIG_IGN (1)",
desc, res.ssr_disp);
bret = false;
}
out:
sa.sa_handler = SIG_DFL;
VERIFY0(sigaction(SIGUSR1, &sa, NULL));
VERIFY0(posix_spawnattr_destroy(&attr));
VERIFY0(posix_spawn_file_actions_destroy(&acts));
VERIFY0(close(pipes[1]));
VERIFY0(close(pipes[0]));
return (bret);
}
static bool
setsigmask_block_test(spawn_attr_test_t *test)
{
const char *desc = test->sat_name;
int pipes[2];
posix_spawn_file_actions_t acts;
posix_spawnattr_t attr;
sigset_t newmask;
sigset_t childmask;
ssize_t n;
bool bret = true;
char *argv[] = { posix_spawn_child_path, "sigmask", NULL };
posix_spawn_pipe_setup(&acts, pipes);
VERIFY0(posix_spawnattr_init(&attr));
VERIFY0(posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK));
(void) sigemptyset(&newmask);
(void) sigaddset(&newmask, SIGUSR1);
(void) sigaddset(&newmask, SIGUSR2);
VERIFY0(posix_spawnattr_setsigmask(&attr, &newmask));
if (!posix_spawn_run_child(desc, posix_spawn_child_path,
&acts, &attr, argv)) {
bret = false;
goto out;
}
n = read(pipes[0], &childmask, sizeof (childmask));
if (n != sizeof (childmask)) {
warnx("TEST FAILED: %s: short read from pipe (%zd)", desc, n);
bret = false;
goto out;
}
if (!sigismember(&childmask, SIGUSR1)) {
warnx("TEST FAILED: %s: SIGUSR1 not in child's signal mask",
desc);
bret = false;
}
if (!sigismember(&childmask, SIGUSR2)) {
warnx("TEST FAILED: %s: SIGUSR2 not in child's signal mask",
desc);
bret = false;
}
out:
VERIFY0(posix_spawnattr_destroy(&attr));
VERIFY0(posix_spawn_file_actions_destroy(&acts));
VERIFY0(close(pipes[1]));
VERIFY0(close(pipes[0]));
return (bret);
}
static bool
setsigmask_empty_test(spawn_attr_test_t *test)
{
const char *desc = test->sat_name;
int pipes[2];
posix_spawn_file_actions_t acts;
posix_spawnattr_t attr;
sigset_t newmask, parentmask;
sigset_t childmask;
ssize_t n;
bool bret = true;
char *argv[] = { posix_spawn_child_path, "sigmask", NULL };
(void) sigemptyset(&newmask);
(void) sigaddset(&newmask, SIGUSR1);
VERIFY0(sigprocmask(SIG_BLOCK, &newmask, &parentmask));
posix_spawn_pipe_setup(&acts, pipes);
VERIFY0(posix_spawnattr_init(&attr));
VERIFY0(posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK));
(void) sigemptyset(&newmask);
VERIFY0(posix_spawnattr_setsigmask(&attr, &newmask));
if (!posix_spawn_run_child(desc, posix_spawn_child_path,
&acts, &attr, argv)) {
bret = false;
goto out;
}
n = read(pipes[0], &childmask, sizeof (childmask));
if (n != sizeof (childmask)) {
warnx("TEST FAILED: %s: short read from pipe (%zd)", desc, n);
bret = false;
goto out;
}
if (sigismember(&childmask, SIGUSR1)) {
warnx("TEST FAILED: %s: "
"SIGUSR1 unexpectedly in child's signal mask", desc);
bret = false;
}
out:
VERIFY0(sigprocmask(SIG_SETMASK, &parentmask, NULL));
VERIFY0(posix_spawnattr_destroy(&attr));
VERIFY0(posix_spawn_file_actions_destroy(&acts));
VERIFY0(close(pipes[1]));
VERIFY0(close(pipes[0]));
return (bret);
}
static bool
setsigign_test(spawn_attr_test_t *test)
{
const char *desc = test->sat_name;
int pipes[2];
posix_spawn_file_actions_t acts;
posix_spawnattr_t attr;
sigset_t sigign;
spawn_sig_result_t res;
ssize_t n;
bool bret = true;
char sig_str[12];
char *argv[] = { posix_spawn_child_path, "sigs", sig_str, NULL };
(void) snprintf(sig_str, sizeof (sig_str), "%d", SIGUSR1);
posix_spawn_pipe_setup(&acts, pipes);
VERIFY0(posix_spawnattr_init(&attr));
VERIFY0(posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGIGN_NP));
(void) sigemptyset(&sigign);
(void) sigaddset(&sigign, SIGUSR1);
VERIFY0(posix_spawnattr_setsigignore_np(&attr, &sigign));
if (!posix_spawn_run_child(desc, posix_spawn_child_path,
&acts, &attr, argv)) {
bret = false;
goto out;
}
n = read(pipes[0], &res, sizeof (res));
if (n != sizeof (res)) {
warnx("TEST FAILED: %s: short read from pipe (%zd)", desc, n);
bret = false;
goto out;
}
if (res.ssr_disp != 1) {
warnx("TEST FAILED: %s: "
"SIGUSR1 disposition is %d, expected SIG_IGN (1)",
desc, res.ssr_disp);
bret = false;
}
out:
VERIFY0(posix_spawnattr_destroy(&attr));
VERIFY0(posix_spawn_file_actions_destroy(&acts));
VERIFY0(close(pipes[1]));
VERIFY0(close(pipes[0]));
return (bret);
}
static bool
sigign_then_sigdef_test(spawn_attr_test_t *test)
{
const char *desc = test->sat_name;
int pipes[2];
posix_spawn_file_actions_t acts;
posix_spawnattr_t attr;
sigset_t sigign, sigdef;
spawn_sig_result_t res;
ssize_t n;
bool bret = true;
char sig_str[12];
char *argv[] = { posix_spawn_child_path, "sigs", sig_str, NULL };
(void) snprintf(sig_str, sizeof (sig_str), "%d", SIGUSR1);
posix_spawn_pipe_setup(&acts, pipes);
VERIFY0(posix_spawnattr_init(&attr));
VERIFY0(posix_spawnattr_setflags(&attr,
POSIX_SPAWN_SETSIGIGN_NP | POSIX_SPAWN_SETSIGDEF));
(void) sigemptyset(&sigign);
(void) sigaddset(&sigign, SIGUSR1);
VERIFY0(posix_spawnattr_setsigignore_np(&attr, &sigign));
(void) sigemptyset(&sigdef);
(void) sigaddset(&sigdef, SIGUSR1);
VERIFY0(posix_spawnattr_setsigdefault(&attr, &sigdef));
if (!posix_spawn_run_child(desc, posix_spawn_child_path,
&acts, &attr, argv)) {
bret = false;
goto out;
}
n = read(pipes[0], &res, sizeof (res));
if (n != sizeof (res)) {
warnx("TEST FAILED: %s: short read from pipe (%zd)", desc, n);
bret = false;
goto out;
}
if (res.ssr_disp != 0) {
warnx("TEST FAILED: %s: "
"SIGUSR1 disposition is %d, expected SIG_DFL (0)",
desc, res.ssr_disp);
bret = false;
}
out:
VERIFY0(posix_spawnattr_destroy(&attr));
VERIFY0(posix_spawn_file_actions_destroy(&acts));
VERIFY0(close(pipes[1]));
VERIFY0(close(pipes[0]));
return (bret);
}
static bool
sigdef_then_sigign_test(spawn_attr_test_t *test)
{
const char *desc = test->sat_name;
int pipes[2];
posix_spawn_file_actions_t acts;
posix_spawnattr_t attr;
sigset_t sigign, sigdef;
spawn_sig_result_t res;
ssize_t n;
bool bret = true;
char sig_str[12];
char *argv[] = { posix_spawn_child_path, "sigs", sig_str, NULL };
(void) snprintf(sig_str, sizeof (sig_str), "%d", SIGUSR1);
posix_spawn_pipe_setup(&acts, pipes);
VERIFY0(posix_spawnattr_init(&attr));
VERIFY0(posix_spawnattr_setflags(&attr,
POSIX_SPAWN_SETSIGIGN_NP | POSIX_SPAWN_SETSIGDEF));
(void) sigemptyset(&sigdef);
(void) sigaddset(&sigdef, SIGUSR1);
VERIFY0(posix_spawnattr_setsigdefault(&attr, &sigdef));
(void) sigemptyset(&sigign);
(void) sigaddset(&sigign, SIGUSR1);
VERIFY0(posix_spawnattr_setsigignore_np(&attr, &sigign));
if (!posix_spawn_run_child(desc, posix_spawn_child_path,
&acts, &attr, argv)) {
bret = false;
goto out;
}
n = read(pipes[0], &res, sizeof (res));
if (n != sizeof (res)) {
warnx("TEST FAILED: %s: short read from pipe (%zd)", desc, n);
bret = false;
goto out;
}
if (res.ssr_disp != 0) {
warnx("TEST FAILED: %s: "
"SIGUSR1 disposition is %d, expected SIG_DFL (0)",
desc, res.ssr_disp);
bret = false;
}
out:
VERIFY0(posix_spawnattr_destroy(&attr));
VERIFY0(posix_spawn_file_actions_destroy(&acts));
VERIFY0(close(pipes[1]));
VERIFY0(close(pipes[0]));
return (bret);
}
static volatile sig_atomic_t sigchld_received = 0;
static void
sigchld_handler(int sig __unused)
{
sigchld_received = 1;
}
static bool
nosigchld_test(spawn_attr_test_t *test)
{
const char *desc = test->sat_name;
bool use_flag = (test->sat_flags & POSIX_SPAWN_NOSIGCHLD_NP) != 0;
posix_spawn_file_actions_t acts;
posix_spawnattr_t attr;
struct sigaction sa, oldsa;
sigset_t mask, oldmask;
pid_t pid;
siginfo_t sig;
int ret;
bool bret = true;
char *true_path = "/usr/bin/true";
char *argv[] = { true_path, NULL };
(void) memset(&sa, 0, sizeof (sa));
sa.sa_handler = sigchld_handler;
sa.sa_flags = SA_NOCLDSTOP;
VERIFY0(sigaction(SIGCHLD, &sa, &oldsa));
(void) sigemptyset(&mask);
(void) sigaddset(&mask, SIGCHLD);
VERIFY0(sigprocmask(SIG_UNBLOCK, &mask, &oldmask));
sigchld_received = 0;
if ((ret = posix_spawn_file_actions_init(&acts)) != 0) {
errc(EXIT_FAILURE, ret, "INTERNAL TEST FAILURE: "
"file_actions_init");
}
VERIFY0(posix_spawnattr_init(&attr));
if (use_flag) {
VERIFY0(posix_spawnattr_setflags(&attr,
POSIX_SPAWN_NOSIGCHLD_NP));
}
ret = posix_spawn(&pid, true_path, &acts, &attr, argv, NULL);
if (ret != 0) {
warnx("TEST FAILED: %s: posix_spawn failed with %s",
desc, strerrorname_np(ret));
bret = false;
goto out;
}
while (waitid(P_PID, pid, &sig, WEXITED) != 0) {
if (errno == EINTR)
continue;
err(EXIT_FAILURE, "INTERNAL TEST ERROR: %s: waitid", desc);
}
if (use_flag) {
if (sigchld_received != 0) {
warnx("TEST FAILED: %s: "
"SIGCHLD was delivered despite NOSIGCHLD_NP flag",
desc);
bret = false;
}
} else {
if (sigchld_received == 0) {
warnx("TEST FAILED: %s: SIGCHLD was NOT delivered",
desc);
bret = false;
}
}
out:
VERIFY0(sigaction(SIGCHLD, &oldsa, NULL));
VERIFY0(sigprocmask(SIG_SETMASK, &oldmask, NULL));
VERIFY0(posix_spawnattr_destroy(&attr));
VERIFY0(posix_spawn_file_actions_destroy(&acts));
return (bret);
}
static bool
noexecerr_test(spawn_attr_test_t *test)
{
const char *desc = test->sat_name;
bool use_flag = (test->sat_flags & POSIX_SPAWN_NOEXECERR_NP) != 0;
posix_spawn_file_actions_t acts;
posix_spawnattr_t attr;
pid_t pid;
int ret;
bool bret = true;
char *bad_path = "/devices/nonexistent/path";
char *argv[] = { bad_path, NULL };
if ((ret = posix_spawn_file_actions_init(&acts)) != 0) {
errc(EXIT_FAILURE, ret,
"INTERNAL TEST FAILURE: file_actions_init");
}
VERIFY0(posix_spawnattr_init(&attr));
if (use_flag) {
VERIFY0(posix_spawnattr_setflags(&attr,
POSIX_SPAWN_NOEXECERR_NP));
}
ret = posix_spawn(&pid, bad_path, &acts, &attr, argv, NULL);
if (use_flag) {
siginfo_t sig;
if (ret != 0) {
warnx("TEST FAILED: %s: "
"posix_spawn returned %s, expected 0",
desc, strerrorname_np(ret));
bret = false;
goto out;
}
if (waitid(P_PID, pid, &sig, WEXITED) != 0) {
err(EXIT_FAILURE, "INTERNAL TEST ERROR: %s: waitid",
desc);
}
if (sig.si_code != CLD_EXITED) {
warnx("TEST FAILED: %s: "
"child did not exit normally: si_code: %d",
desc, sig.si_code);
bret = false;
goto out;
}
if (sig.si_status != 127) {
warnx("TEST FAILED: %s: "
"child exited with status %d, expected 127",
desc, sig.si_status);
bret = false;
}
} else {
if (ret == 0) {
siginfo_t sig;
warnx("TEST FAILED: %s: "
"posix_spawn returned 0, expected an error", desc);
(void) waitid(P_PID, pid, &sig, WEXITED);
bret = false;
goto out;
}
if (ret != ENOENT) {
warnx("TEST FAILED: %s: "
"posix_spawn returned %s, expected ENOENT",
desc, strerrorname_np(ret));
bret = false;
}
}
out:
VERIFY0(posix_spawnattr_destroy(&attr));
VERIFY0(posix_spawn_file_actions_destroy(&acts));
return (bret);
}
static bool
bad_flags_test(spawn_attr_test_t *test)
{
posix_spawnattr_t attr;
int ret;
VERIFY0(posix_spawnattr_init(&attr));
ret = posix_spawnattr_setflags(&attr, (short)0x8000);
if (ret != EINVAL) {
warnx("TEST FAILED: %s: "
"setflags(0x8000) returned %s, expected EINVAL",
test->sat_name, strerrorname_np(ret));
VERIFY0(posix_spawnattr_destroy(&attr));
return (false);
}
VERIFY0(posix_spawnattr_destroy(&attr));
return (true);
}
static bool
bad_flags_mixed_test(spawn_attr_test_t *test)
{
posix_spawnattr_t attr;
int ret;
VERIFY0(posix_spawnattr_init(&attr));
ret = posix_spawnattr_setflags(&attr,
POSIX_SPAWN_SETSIGDEF | (short)0x8000);
if (ret != EINVAL) {
warnx("TEST FAILED: %s: "
"setflags(SETSIGDEF|0x8000) returned %s, expected EINVAL",
test->sat_name, strerrorname_np(ret));
VERIFY0(posix_spawnattr_destroy(&attr));
return (false);
}
VERIFY0(posix_spawnattr_destroy(&attr));
return (true);
}
static void
sigusr1_nop(int sig __unused)
{
}
static bool
caught_handler_reset_test(spawn_attr_test_t *test)
{
const char *desc = test->sat_name;
int pipes[2];
posix_spawn_file_actions_t acts;
struct sigaction sa, oldsa;
spawn_sig_result_t res;
ssize_t n;
bool bret = true;
char sig_str[12];
char *argv[] = { posix_spawn_child_path, "sigs", sig_str, NULL };
(void) snprintf(sig_str, sizeof (sig_str), "%d", SIGUSR1);
(void) memset(&sa, 0, sizeof (sa));
sa.sa_handler = sigusr1_nop;
VERIFY0(sigaction(SIGUSR1, &sa, &oldsa));
posix_spawn_pipe_setup(&acts, pipes);
if (!posix_spawn_run_child(desc, posix_spawn_child_path,
&acts, NULL, argv)) {
bret = false;
goto out;
}
n = read(pipes[0], &res, sizeof (res));
if (n != sizeof (res)) {
warnx("TEST FAILED: %s: short read from pipe (%zd)", desc, n);
bret = false;
goto out;
}
if (res.ssr_disp != 0) {
warnx("TEST FAILED: %s: "
"SIGUSR1 disposition is %d, expected SIG_DFL (0)",
desc, res.ssr_disp);
bret = false;
}
out:
VERIFY0(sigaction(SIGUSR1, &oldsa, NULL));
VERIFY0(posix_spawn_file_actions_destroy(&acts));
VERIFY0(close(pipes[1]));
VERIFY0(close(pipes[0]));
return (bret);
}
static spawn_attr_test_t tests[] = {
{ .sat_name = "SETSIGDEF: SIGUSR1+SIGUSR2 reset to SIG_DFL",
.sat_func = setsigdef_test },
{ .sat_name = "SETSIGDEF: empty set, no change",
.sat_func = setsigdef_empty_test },
{ .sat_name = "SETSIGMASK: block SIGUSR1+SIGUSR2",
.sat_func = setsigmask_block_test },
{ .sat_name = "SETSIGMASK: parent blocks SIGUSR1, child empty",
.sat_func = setsigmask_empty_test },
{ .sat_name = "SETSIGIGN_NP: SIGUSR1 set to SIG_IGN",
.sat_func = setsigign_test },
{ .sat_name = "SETSIGIGN_NP + SETSIGDEF: SETSIGDEF wins (ign first)",
.sat_func = sigign_then_sigdef_test },
{ .sat_name = "SETSIGIGN_NP + SETSIGDEF: SETSIGDEF wins (def first)",
.sat_func = sigdef_then_sigign_test },
{ .sat_name = "exec resets caught handler to SIG_DFL",
.sat_func = caught_handler_reset_test },
{ .sat_name = "NOSIGCHLD_NP: no SIGCHLD delivered",
.sat_func = nosigchld_test,
.sat_flags = POSIX_SPAWN_NOSIGCHLD_NP },
{ .sat_name = "NOSIGCHLD_NP control: SIGCHLD delivered normally",
.sat_func = nosigchld_test },
{ .sat_name = "NOEXECERR_NP: bad path returns 0, child exits 127",
.sat_func = noexecerr_test,
.sat_flags = POSIX_SPAWN_NOEXECERR_NP },
{ .sat_name = "NOEXECERR_NP control: bad path returns error",
.sat_func = noexecerr_test },
{ .sat_name = "setflags: reject invalid flag bits",
.sat_func = bad_flags_test },
{ .sat_name = "setflags: reject valid+invalid flag mix",
.sat_func = bad_flags_mixed_test },
};
int
main(void)
{
const char *helpers[] = { POSIX_SPAWN_CHILD_HELPERS };
int ret = EXIT_SUCCESS;
for (size_t h = 0; h < ARRAY_SIZE(helpers); h++) {
posix_spawn_find_helper(posix_spawn_child_path,
sizeof (posix_spawn_child_path), helpers[h]);
(void) printf("--- child helper: %s ---\n", helpers[h]);
for (size_t i = 0; i < ARRAY_SIZE(tests); i++) {
if (tests[i].sat_func(&tests[i])) {
(void) printf("TEST PASSED: %s\n",
tests[i].sat_name);
} else {
ret = EXIT_FAILURE;
}
}
}
if (ret == EXIT_SUCCESS)
(void) printf("All tests passed successfully!\n");
return (ret);
}