#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <libproc.h>
#include <pthread.h>
#include <signal.h>
#include <spawn.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/debug.h>
#include <sys/param.h>
#include <sys/procfs.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
extern char **environ;
static char fifopath[MAXPATHLEN];
static pid_t spawn_pid = -1;
static int spawn_ret = -1;
static pid_t child_pid = -1;
static void *
spawn_thread(void *arg __unused)
{
posix_spawn_file_actions_t fa;
char *argv[] = { "true", NULL };
VERIFY0(posix_spawn_file_actions_init(&fa));
VERIFY0(posix_spawn_file_actions_addopen(&fa, 3, fifopath,
O_RDONLY, 0));
spawn_ret = posix_spawn(&spawn_pid, "/usr/bin/true", &fa, NULL,
argv, environ);
VERIFY0(posix_spawn_file_actions_destroy(&fa));
return (NULL);
}
static int
child_walker(psinfo_t *psp, lwpsinfo_t *lsp __unused, void *arg)
{
pid_t parent = *(pid_t *)arg;
if (psp->pr_ppid != parent)
return (0);
child_pid = psp->pr_pid;
return (1);
}
static bool
wait_for_child(void)
{
pid_t self = getpid();
for (int i = 0; i < 10000; i++) {
int found = proc_walk(child_walker, &self, PR_WALK_PROC);
if (found == -1)
err(EXIT_FAILURE, "could not walk /proc");
if (found != 0)
return (true);
(void) usleep(1000);
}
return (false);
}
static bool
expect_ctl_ebusy(pid_t pid)
{
char path[MAXPATHLEN];
long cmd = PCSTOP;
bool ret = false;
int fd;
(void) snprintf(path, sizeof (path), "/proc/%" _PRIdID "/ctl", pid);
if ((fd = open(path, O_WRONLY)) == -1) {
warn("TEST FAILED: could not open %s", path);
return (false);
}
if (write(fd, &cmd, sizeof (cmd)) != -1) {
warnx("TEST FAILED: PCSTOP on a spawning child unexpectedly "
"succeeded");
} else if (errno != EBUSY) {
warn("TEST FAILED: PCSTOP returned an unexpected error");
} else {
ret = true;
}
VERIFY0(close(fd));
return (ret);
}
static void
alarm_handler(int sig __unused)
{
}
int
main(void)
{
struct sigaction sa = { .sa_handler = alarm_handler };
pthread_t tid;
int ret = EXIT_SUCCESS;
int fd, status;
pid_t pid;
if (snprintf(fifopath, sizeof (fifopath),
"/tmp/posix_spawn_wait.%" _PRIdID, getpid()) >= sizeof (fifopath))
errx(EXIT_FAILURE, "FIFO path too long");
(void) unlink(fifopath);
if (mkfifo(fifopath, 0600) != 0)
err(EXIT_FAILURE, "could not create FIFO %s", fifopath);
VERIFY0(sigaction(SIGALRM, &sa, NULL));
VERIFY0(pthread_create(&tid, NULL, spawn_thread, NULL));
if (!wait_for_child()) {
warnx("TEST FAILED: spawn child never appeared");
ret = EXIT_FAILURE;
goto release;
}
if (expect_ctl_ebusy(child_pid)) {
(void) printf("TEST PASSED: /proc control of a spawning child "
"refused with EBUSY\n");
} else {
ret = EXIT_FAILURE;
}
(void) alarm(10);
pid = fork();
(void) alarm(0);
switch (pid) {
case -1:
if (errno == EINTR) {
warnx("TEST FAILED: fork blocked while a sibling "
"thread was in spawn(2)");
} else {
warn("TEST FAILED: fork failed");
}
ret = EXIT_FAILURE;
break;
case 0:
_exit(0);
default:
if (waitpid(pid, &status, 0) != pid ||
!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
warnx("TEST FAILED: fork child did not exit cleanly");
ret = EXIT_FAILURE;
} else {
(void) printf("TEST PASSED: fork while sibling "
"thread blocked in spawn\n");
}
break;
}
release:
(void) alarm(30);
if ((fd = open(fifopath, O_WRONLY)) == -1) {
warnx("TEST FAILED: could not open FIFO for writing");
(void) unlink(fifopath);
return (EXIT_FAILURE);
}
(void) alarm(0);
VERIFY0(close(fd));
VERIFY0(pthread_join(tid, NULL));
if (spawn_ret != 0) {
warnx("TEST FAILED: posix_spawn returned %d (%s)",
spawn_ret, strerror(spawn_ret));
ret = EXIT_FAILURE;
} else if (waitpid(spawn_pid, &status, 0) != spawn_pid ||
!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
warnx("TEST FAILED: spawned child status %#x", status);
ret = EXIT_FAILURE;
} else {
(void) printf("TEST PASSED: spawn completed after the "
"child was released\n");
}
(void) unlink(fifopath);
if (ret == EXIT_SUCCESS)
(void) printf("All tests passed successfully!\n");
return (ret);
}