#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <spawn.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>
#include <sys/sysmacros.h>
#include <sys/debug.h>
#include "posix_spawn_common.h"
#define SFT_MAXFDS 4
typedef struct spawn_fa_test spawn_fa_test_t;
typedef bool (*action_setup_fn_t)(spawn_fa_test_t *,
posix_spawn_file_actions_t *);
typedef bool (*action_pre_fn_t)(spawn_fa_test_t *);
struct spawn_fa_test {
const char *sft_name;
action_setup_fn_t sft_setup;
size_t sft_nfds;
int sft_fds[SFT_MAXFDS];
spawn_fd_result_t sft_expected[SFT_MAXFDS];
int sft_open_fd;
const char *sft_open_path;
int sft_open_flags;
int sft_close_fd;
int sft_dup2_from;
int sft_dup2_to;
action_pre_fn_t sft_pre;
int sft_parent_fd;
int sft_expect_errno;
};
static char posix_spawn_child_path[PATH_MAX];
static bool
spawn_verify_fds(const char *desc, int pipefd,
const spawn_fd_result_t *expected, size_t count)
{
bool ret = true;
for (size_t i = 0; i < count; i++) {
spawn_fd_result_t res;
ssize_t n;
n = read(pipefd, &res, sizeof (res));
if (n != sizeof (res)) {
warnx("TEST FAILED: %s: "
"failed to read result %zu from pipe "
"(got %zd bytes)", desc, i, n);
return (false);
}
if (res.srf_fd != expected[i].srf_fd) {
warnx("TEST FAILED: %s: "
"result %zu: expected fd %d, got %d",
desc, i, expected[i].srf_fd, res.srf_fd);
ret = false;
continue;
}
if (res.srf_open != expected[i].srf_open) {
warnx("TEST FAILED: %s: fd %d: expected %s, got %s",
desc, res.srf_fd,
expected[i].srf_open ? "open" : "closed",
res.srf_open ? "open" : "closed");
ret = false;
continue;
}
if (expected[i].srf_open &&
res.srf_flags != expected[i].srf_flags) {
warnx("TEST FAILED: %s: fd %d: expected flags 0x%x, "
"got 0x%x", desc, res.srf_fd,
expected[i].srf_flags, res.srf_flags);
ret = false;
continue;
}
}
return (ret);
}
static bool
spawn_test_fds(spawn_fa_test_t *test)
{
const char *desc = test->sft_name;
int pipes[2];
posix_spawn_file_actions_t acts;
bool ret = false;
char fd_strs[SFT_MAXFDS][12];
char *argv[SFT_MAXFDS + 3];
size_t ai = 0;
test->sft_parent_fd = -1;
if (test->sft_pre != NULL && !test->sft_pre(test))
goto cleanup;
posix_spawn_pipe_setup(&acts, pipes);
if (test->sft_setup != NULL && !test->sft_setup(test, &acts)) {
VERIFY0(posix_spawn_file_actions_destroy(&acts));
VERIFY0(close(pipes[0]));
VERIFY0(close(pipes[1]));
goto cleanup;
}
argv[ai++] = posix_spawn_child_path;
argv[ai++] = "fds";
for (size_t i = 0; i < test->sft_nfds; i++) {
(void) snprintf(fd_strs[i], sizeof (fd_strs[i]), "%d",
test->sft_fds[i]);
argv[ai++] = fd_strs[i];
}
argv[ai] = NULL;
if (test->sft_expect_errno != 0) {
pid_t pid;
int err;
err = posix_spawn(&pid, posix_spawn_child_path, &acts, NULL,
argv, NULL);
if (err == 0) {
siginfo_t sig;
warnx("TEST FAILED: %s: "
"posix_spawn unexpectedly succeeded", desc);
(void) waitid(P_PID, pid, &sig, WEXITED);
} else if (err != test->sft_expect_errno) {
warnx("TEST FAILED: %s: "
"posix_spawn returned %s, expected %s",
desc, strerrorname_np(err),
strerrorname_np(test->sft_expect_errno));
} else {
ret = true;
}
} else {
ret = posix_spawn_run_child(desc, posix_spawn_child_path,
&acts, NULL, argv);
if (ret) {
ret = spawn_verify_fds(desc, pipes[0],
test->sft_expected, test->sft_nfds);
}
}
VERIFY0(posix_spawn_file_actions_destroy(&acts));
VERIFY0(close(pipes[1]));
VERIFY0(close(pipes[0]));
cleanup:
if (test->sft_parent_fd >= 0)
(void) close(test->sft_parent_fd);
return (ret);
}
static bool
addopen_setup(spawn_fa_test_t *test, posix_spawn_file_actions_t *acts)
{
int ret = posix_spawn_file_actions_addopen(acts, test->sft_open_fd,
test->sft_open_path, test->sft_open_flags, 0);
if (ret != 0) {
warnx("TEST FAILED: %s: addopen failed with %s",
test->sft_name, strerrorname_np(ret));
return (false);
}
return (true);
}
static bool
addclose_setup(spawn_fa_test_t *test, posix_spawn_file_actions_t *acts)
{
int ret = posix_spawn_file_actions_addclose(acts, test->sft_close_fd);
if (ret != 0) {
warnx("TEST FAILED: %s: addclose failed with %s",
test->sft_name, strerrorname_np(ret));
return (false);
}
return (true);
}
static bool
adddup2_setup(spawn_fa_test_t *test, posix_spawn_file_actions_t *acts)
{
int ret = posix_spawn_file_actions_adddup2(acts, test->sft_dup2_from,
test->sft_dup2_to);
if (ret != 0) {
warnx("TEST FAILED: %s: adddup2 failed with %s",
test->sft_name, strerrorname_np(ret));
return (false);
}
return (true);
}
static bool
close_action_fd_setup(spawn_fa_test_t *test,
posix_spawn_file_actions_t *acts)
{
const char *desc = test->sft_name;
int ret;
ret = posix_spawn_file_actions_addopen(acts, 10,
"/dev/null", O_RDONLY, 0);
if (ret != 0) {
warnx("TEST FAILED: %s: addopen failed with %s",
desc, strerrorname_np(ret));
return (false);
}
ret = posix_spawn_file_actions_addclose(acts, 10);
if (ret != 0) {
warnx("TEST FAILED: %s: addclose failed with %s",
desc, strerrorname_np(ret));
return (false);
}
return (true);
}
static bool
close_badfd_setup(spawn_fa_test_t *test, posix_spawn_file_actions_t *acts)
{
const char *desc = test->sft_name;
int ret;
ret = posix_spawn_file_actions_addclose(acts, test->sft_close_fd);
if (ret != 0) {
warnx("TEST FAILED: %s: addclose failed with %s",
desc, strerrorname_np(ret));
return (false);
}
ret = posix_spawn_file_actions_addopen(acts, 10,
"/dev/null", O_RDONLY, 0);
if (ret != 0) {
warnx("TEST FAILED: %s: addopen failed with %s",
desc, strerrorname_np(ret));
return (false);
}
return (true);
}
static bool
closefrom_setup(spawn_fa_test_t *test, posix_spawn_file_actions_t *acts)
{
const char *desc = test->sft_name;
int ret;
for (int fd = 10; fd <= 13; fd++) {
ret = posix_spawn_file_actions_addopen(acts, fd,
"/dev/null", O_RDONLY, 0);
if (ret != 0) {
warnx("TEST FAILED: %s: addopen(%d) failed with %s",
desc, fd, strerrorname_np(ret));
return (false);
}
}
ret = posix_spawn_file_actions_addclosefrom_np(acts, 11);
if (ret != 0) {
warnx("TEST FAILED: %s: addclosefrom_np failed with %s",
desc, strerrorname_np(ret));
return (false);
}
return (true);
}
static bool
closefrom_then_open_setup(spawn_fa_test_t *test,
posix_spawn_file_actions_t *acts)
{
const char *desc = test->sft_name;
int ret;
ret = posix_spawn_file_actions_addclosefrom_np(acts, 3);
if (ret != 0) {
warnx("TEST FAILED: %s: addclosefrom_np failed with %s",
desc, strerrorname_np(ret));
return (false);
}
ret = posix_spawn_file_actions_addopen(acts, 10,
"/dev/null", O_RDONLY, 0);
if (ret != 0) {
warnx("TEST FAILED: %s: addopen failed with %s",
desc, strerrorname_np(ret));
return (false);
}
return (true);
}
static bool
open_dup_close_setup(spawn_fa_test_t *test, posix_spawn_file_actions_t *acts)
{
const char *desc = test->sft_name;
int ret;
ret = posix_spawn_file_actions_addopen(acts, 10,
"/dev/null", O_RDONLY, 0);
if (ret != 0) {
warnx("TEST FAILED: %s: addopen failed with %s",
desc, strerrorname_np(ret));
return (false);
}
ret = posix_spawn_file_actions_adddup2(acts, 10, 20);
if (ret != 0) {
warnx("TEST FAILED: %s: adddup2 failed with %s",
desc, strerrorname_np(ret));
return (false);
}
ret = posix_spawn_file_actions_addclose(acts, 10);
if (ret != 0) {
warnx("TEST FAILED: %s: addclose failed with %s",
desc, strerrorname_np(ret));
return (false);
}
return (true);
}
static bool
multi_open_setup(spawn_fa_test_t *test, posix_spawn_file_actions_t *acts)
{
const char *desc = test->sft_name;
int ret;
ret = posix_spawn_file_actions_addopen(acts, 10, "/dev/null",
O_RDONLY, 0);
if (ret != 0) {
warnx("TEST FAILED: %s: addopen(10) failed with %s",
desc, strerrorname_np(ret));
return (false);
}
ret = posix_spawn_file_actions_addopen(acts, 11, "/dev/null",
O_WRONLY, 0);
if (ret != 0) {
warnx("TEST FAILED: %s: addopen(11) failed with %s",
desc, strerrorname_np(ret));
return (false);
}
ret = posix_spawn_file_actions_addopen(acts, 12, "/dev/null",
O_RDWR, 0);
if (ret != 0) {
warnx("TEST FAILED: %s: addopen(12) failed with %s",
desc, strerrorname_np(ret));
return (false);
}
return (true);
}
static bool
spawn_open_parent_fd(spawn_fa_test_t *test, int fdflags)
{
int fd = open("/dev/null", O_RDONLY);
if (fd < 0) {
warnx("INTERNAL TEST ERROR: %s: open /dev/null failed: %s",
test->sft_name, strerrorname_np(errno));
return (false);
}
if (fdflags != 0 && fcntl(fd, F_SETFD, fdflags) != 0) {
warnx("INTERNAL TEST ERROR: %s: F_SETFD failed: %s",
test->sft_name, strerrorname_np(errno));
(void) close(fd);
return (false);
}
test->sft_parent_fd = fd;
return (true);
}
static bool
clofork_present_pre(spawn_fa_test_t *test)
{
if (!spawn_open_parent_fd(test, FD_CLOFORK))
return (false);
test->sft_fds[0] = test->sft_parent_fd;
test->sft_expected[0].srf_fd = test->sft_parent_fd;
return (true);
}
static bool
cloexec_present_pre(spawn_fa_test_t *test)
{
if (!spawn_open_parent_fd(test, FD_CLOEXEC))
return (false);
test->sft_fds[0] = test->sft_parent_fd;
test->sft_expected[0].srf_fd = test->sft_parent_fd;
return (true);
}
static bool
clofork_dup_action_pre(spawn_fa_test_t *test)
{
if (!spawn_open_parent_fd(test, FD_CLOFORK))
return (false);
test->sft_dup2_from = test->sft_parent_fd;
return (true);
}
static bool
cloexec_dup_action_pre(spawn_fa_test_t *test)
{
if (!spawn_open_parent_fd(test, FD_CLOEXEC))
return (false);
test->sft_dup2_from = test->sft_parent_fd;
test->sft_fds[0] = test->sft_parent_fd;
test->sft_expected[0].srf_fd = test->sft_parent_fd;
return (true);
}
static spawn_fa_test_t tests[] = {
{ .sft_name = "addopen /dev/null O_RDONLY on fd 10",
.sft_setup = addopen_setup,
.sft_nfds = 1, .sft_fds = { 10 },
.sft_expected = {
{ .srf_fd = 10, .srf_open = 1, .srf_flags = O_RDONLY } },
.sft_open_fd = 10, .sft_open_path = "/dev/null",
.sft_open_flags = O_RDONLY },
{ .sft_name = "addopen /dev/null O_WRONLY on fd 11",
.sft_setup = addopen_setup,
.sft_nfds = 1, .sft_fds = { 11 },
.sft_expected = {
{ .srf_fd = 11, .srf_open = 1, .srf_flags = O_WRONLY } },
.sft_open_fd = 11, .sft_open_path = "/dev/null",
.sft_open_flags = O_WRONLY },
{ .sft_name = "addopen /dev/null O_RDWR on fd 12",
.sft_setup = addopen_setup,
.sft_nfds = 1, .sft_fds = { 12 },
.sft_expected = {
{ .srf_fd = 12, .srf_open = 1, .srf_flags = O_RDWR } },
.sft_open_fd = 12, .sft_open_path = "/dev/null",
.sft_open_flags = O_RDWR },
{ .sft_name = "addopen onto STDIN_FILENO (replace stdin)",
.sft_setup = addopen_setup,
.sft_nfds = 1, .sft_fds = { STDIN_FILENO },
.sft_expected = {
{ .srf_fd = 0, .srf_open = 1, .srf_flags = O_RDWR } },
.sft_open_fd = STDIN_FILENO, .sft_open_path = "/dev/null",
.sft_open_flags = O_RDWR },
{ .sft_name = "addopen /dev/null on high fd (50)",
.sft_setup = addopen_setup,
.sft_nfds = 1, .sft_fds = { 50 },
.sft_expected = {
{ .srf_fd = 50, .srf_open = 1, .srf_flags = O_RDONLY } },
.sft_open_fd = 50, .sft_open_path = "/dev/null",
.sft_open_flags = O_RDONLY },
{ .sft_name = "addclose fd opened by prior action",
.sft_setup = close_action_fd_setup,
.sft_nfds = 1, .sft_fds = { 10 },
.sft_expected = {
{ .srf_fd = 10, .srf_open = 0 } } },
{ .sft_name = "addclose STDIN_FILENO",
.sft_setup = addclose_setup,
.sft_nfds = 1, .sft_fds = { STDIN_FILENO },
.sft_expected = {
{ .srf_fd = 0, .srf_open = 0 } },
.sft_close_fd = STDIN_FILENO },
{ .sft_name = "adddup2 STDOUT to fd 20",
.sft_setup = adddup2_setup,
.sft_nfds = 1, .sft_fds = { 20 },
.sft_expected = {
{ .srf_fd = 20, .srf_open = 1, .srf_flags = O_RDWR } },
.sft_dup2_from = STDOUT_FILENO, .sft_dup2_to = 20 },
{ .sft_name = "adddup2 to self (fd 0 -> fd 0)",
.sft_setup = adddup2_setup,
.sft_nfds = 1, .sft_fds = { STDIN_FILENO },
.sft_expected = {
{ .srf_fd = 0, .srf_open = 1, .srf_flags = O_RDONLY } },
.sft_dup2_from = STDIN_FILENO, .sft_dup2_to = STDIN_FILENO },
{ .sft_name = "addclosefrom_np(11) with fds 10-13",
.sft_setup = closefrom_setup,
.sft_nfds = 4, .sft_fds = { 10, 11, 12, 13 },
.sft_expected = {
{ .srf_fd = 10, .srf_open = 1, .srf_flags = O_RDONLY },
{ .srf_fd = 11, .srf_open = 0 },
{ .srf_fd = 12, .srf_open = 0 },
{ .srf_fd = 13, .srf_open = 0 } } },
{ .sft_name = "closefrom(3) then open fd 10",
.sft_setup = closefrom_then_open_setup,
.sft_nfds = 1, .sft_fds = { 10 },
.sft_expected = {
{ .srf_fd = 10, .srf_open = 1, .srf_flags = O_RDONLY } } },
{ .sft_name = "open fd 10, dup2 10->20, close 10",
.sft_setup = open_dup_close_setup,
.sft_nfds = 2, .sft_fds = { 10, 20 },
.sft_expected = {
{ .srf_fd = 10, .srf_open = 0 },
{ .srf_fd = 20, .srf_open = 1, .srf_flags = O_RDONLY } } },
{ .sft_name = "multiple opens on fds 10, 11, 12",
.sft_setup = multi_open_setup,
.sft_nfds = 3, .sft_fds = { 10, 11, 12 },
.sft_expected = {
{ .srf_fd = 10, .srf_open = 1, .srf_flags = O_RDONLY },
{ .srf_fd = 11, .srf_open = 1, .srf_flags = O_WRONLY },
{ .srf_fd = 12, .srf_open = 1, .srf_flags = O_RDWR } } },
{ .sft_name = "FD_CLOFORK descriptor not inherited by child",
.sft_pre = clofork_present_pre,
.sft_nfds = 1, .sft_fds = { -1 },
.sft_expected = {
{ .srf_fd = -1, .srf_open = 0 } } },
{ .sft_name = "FD_CLOEXEC descriptor not inherited by child",
.sft_pre = cloexec_present_pre,
.sft_nfds = 1, .sft_fds = { -1 },
.sft_expected = {
{ .srf_fd = -1, .srf_open = 0 } } },
{ .sft_name = "adddup2 of FD_CLOFORK fd fails with EBADF",
.sft_pre = clofork_dup_action_pre,
.sft_setup = adddup2_setup,
.sft_dup2_from = -1, .sft_dup2_to = 20,
.sft_expect_errno = EBADF },
{ .sft_name = "adddup2 of FD_CLOEXEC fd produces persistent fd",
.sft_pre = cloexec_dup_action_pre,
.sft_setup = adddup2_setup,
.sft_dup2_from = -1, .sft_dup2_to = 20,
.sft_nfds = 2, .sft_fds = { -1, 20 },
.sft_expected = {
{ .srf_fd = -1, .srf_open = 0 },
{ .srf_fd = 20, .srf_open = 1, .srf_flags = O_RDONLY } } },
{ .sft_name = "addopen of nonexistent path fails with ENOENT",
.sft_setup = addopen_setup,
.sft_open_fd = 10,
.sft_open_path = "/devices/nonexistent/posix_spawn_test",
.sft_open_flags = O_RDONLY,
.sft_expect_errno = ENOENT },
{ .sft_name = "addopen onto fd 2 of nonexistent path fails ENOENT",
.sft_setup = addopen_setup,
.sft_open_fd = 2,
.sft_open_path = "/devices/nonexistent/posix_spawn_test",
.sft_open_flags = O_RDONLY,
.sft_expect_errno = ENOENT },
{ .sft_name = "addopen onto INT32_MAX fails with EBADF",
.sft_setup = addopen_setup,
.sft_open_fd = INT32_MAX, .sft_open_path = "/dev/null",
.sft_open_flags = O_RDONLY,
.sft_expect_errno = EBADF },
{ .sft_name = "adddup2 from INT32_MAX fails with EBADF",
.sft_setup = adddup2_setup,
.sft_dup2_from = INT32_MAX, .sft_dup2_to = 20,
.sft_expect_errno = EBADF },
{ .sft_name = "adddup2 to INT32_MAX fails with EBADF",
.sft_setup = adddup2_setup,
.sft_dup2_from = STDOUT_FILENO, .sft_dup2_to = INT32_MAX,
.sft_expect_errno = EBADF },
{ .sft_name = "addclose of INT32_MAX (not open) is ignored",
.sft_setup = close_badfd_setup,
.sft_nfds = 1, .sft_fds = { 10 },
.sft_expected = {
{ .srf_fd = 10, .srf_open = 1, .srf_flags = O_RDONLY } },
.sft_close_fd = INT32_MAX },
};
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 (spawn_test_fds(&tests[i])) {
(void) printf("TEST PASSED: %s\n",
tests[i].sft_name);
} else {
ret = EXIT_FAILURE;
}
}
}
if (ret == EXIT_SUCCESS)
(void) printf("All tests passed successfully!\n");
return (ret);
}