#include "SDL_internal.h"
#ifdef SDL_PROCESS_POSIX
#include <dirent.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include "../SDL_sysprocess.h"
#include "../../io/SDL_iostream_c.h"
#if defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP) && \
!defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR)
#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR
#define posix_spawn_file_actions_addchdir posix_spawn_file_actions_addchdir_np
#endif
#define READ_END 0
#define WRITE_END 1
struct SDL_ProcessData {
pid_t pid;
};
static void CleanupStream(void *userdata, void *value)
{
SDL_Process *process = (SDL_Process *)value;
const char *property = (const char *)userdata;
SDL_ClearProperty(process->props, property);
}
static bool SetupStream(SDL_Process *process, int fd, const char *mode, const char *property)
{
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
SDL_IOStream *io = SDL_IOFromFD(fd, true);
if (!io) {
return false;
}
SDL_SetPointerPropertyWithCleanup(SDL_GetIOProperties(io), "SDL.internal.process", process, CleanupStream, (void *)property);
SDL_SetPointerProperty(process->props, property, io);
return true;
}
static void IgnoreSignal(int sig)
{
struct sigaction action;
sigaction(SIGPIPE, NULL, &action);
#ifdef HAVE_SA_SIGACTION
if (action.sa_handler == SIG_DFL && (void (*)(int))action.sa_sigaction == SIG_DFL) {
#else
if (action.sa_handler == SIG_DFL) {
#endif
action.sa_handler = SIG_IGN;
sigaction(sig, &action, NULL);
}
}
static bool CreatePipe(int fds[2])
{
if (pipe(fds) < 0) {
return false;
}
fcntl(fds[READ_END], F_SETFD, fcntl(fds[READ_END], F_GETFD) | FD_CLOEXEC);
fcntl(fds[WRITE_END], F_SETFD, fcntl(fds[WRITE_END], F_GETFD) | FD_CLOEXEC);
IgnoreSignal(SIGPIPE);
return true;
}
static bool GetStreamFD(SDL_PropertiesID props, const char *property, int *result)
{
SDL_IOStream *io = (SDL_IOStream *)SDL_GetPointerProperty(props, property, NULL);
if (!io) {
SDL_SetError("%s is not set", property);
return false;
}
int fd = (int)SDL_GetNumberProperty(SDL_GetIOProperties(io), SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER, -1);
if (fd < 0) {
SDL_SetError("%s doesn't have SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER available", property);
return false;
}
*result = fd;
return true;
}
static bool AddFileDescriptorCloseActions(posix_spawn_file_actions_t *fa)
{
DIR *dir = opendir("/proc/self/fd");
if (dir) {
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
int fd = SDL_atoi(entry->d_name);
if (fd <= STDERR_FILENO) {
continue;
}
int flags = fcntl(fd, F_GETFD);
if (flags < 0 || (flags & FD_CLOEXEC)) {
continue;
}
if (posix_spawn_file_actions_addclose(fa, fd) != 0) {
closedir(dir);
return SDL_SetError("posix_spawn_file_actions_addclose failed: %s", strerror(errno));
}
}
closedir(dir);
} else {
for (int fd = (int)(sysconf(_SC_OPEN_MAX) - 1); fd > STDERR_FILENO; --fd) {
int flags = fcntl(fd, F_GETFD);
if (flags < 0 || (flags & FD_CLOEXEC)) {
continue;
}
if (posix_spawn_file_actions_addclose(fa, fd) != 0) {
return SDL_SetError("posix_spawn_file_actions_addclose failed: %s", strerror(errno));
}
}
}
return true;
}
bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID props)
{
char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL);
SDL_Environment *env = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, SDL_GetEnvironment());
char **envp = NULL;
const char *working_directory = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING, NULL);
SDL_ProcessIO stdin_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_NULL);
SDL_ProcessIO stdout_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_INHERITED);
SDL_ProcessIO stderr_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_INHERITED);
bool redirect_stderr = SDL_GetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN, false) &&
!SDL_HasProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER);
int stdin_pipe[2] = { -1, -1 };
int stdout_pipe[2] = { -1, -1 };
int stderr_pipe[2] = { -1, -1 };
int fd = -1;
envp = SDL_GetEnvironmentVariables(env);
if (!envp) {
return false;
}
SDL_ProcessData *data = SDL_calloc(1, sizeof(*data));
if (!data) {
SDL_free(envp);
return false;
}
process->internal = data;
posix_spawnattr_t attr;
posix_spawn_file_actions_t fa;
if (posix_spawnattr_init(&attr) != 0) {
SDL_SetError("posix_spawnattr_init failed: %s", strerror(errno));
goto posix_spawn_fail_none;
}
if (posix_spawn_file_actions_init(&fa) != 0) {
SDL_SetError("posix_spawn_file_actions_init failed: %s", strerror(errno));
goto posix_spawn_fail_attr;
}
if (working_directory) {
#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR
#ifdef SDL_PLATFORM_APPLE
if (__builtin_available(macOS 10.15, *)) {
if (posix_spawn_file_actions_addchdir_np(&fa, working_directory) != 0) {
SDL_SetError("posix_spawn_file_actions_addchdir failed: %s", strerror(errno));
goto posix_spawn_fail_all;
}
} else {
SDL_SetError("Setting the working directory is only supported on macOS 10.15 and newer");
goto posix_spawn_fail_all;
}
#else
if (posix_spawn_file_actions_addchdir(&fa, working_directory) != 0) {
SDL_SetError("posix_spawn_file_actions_addchdir failed: %s", strerror(errno));
goto posix_spawn_fail_all;
}
#endif #else
SDL_SetError("Setting the working directory is not supported");
goto posix_spawn_fail_all;
#endif
}
if (process->background) {
if (stdin_option == SDL_PROCESS_STDIO_INHERITED) {
stdin_option = SDL_PROCESS_STDIO_NULL;
}
if (stdout_option == SDL_PROCESS_STDIO_INHERITED) {
stdout_option = SDL_PROCESS_STDIO_NULL;
}
if (stderr_option == SDL_PROCESS_STDIO_INHERITED) {
stderr_option = SDL_PROCESS_STDIO_NULL;
}
}
switch (stdin_option) {
case SDL_PROCESS_STDIO_REDIRECT:
if (!GetStreamFD(props, SDL_PROP_PROCESS_CREATE_STDIN_POINTER, &fd)) {
goto posix_spawn_fail_all;
}
if (posix_spawn_file_actions_adddup2(&fa, fd, STDIN_FILENO) != 0) {
SDL_SetError("posix_spawn_file_actions_adddup2 failed: %s", strerror(errno));
goto posix_spawn_fail_all;
}
break;
case SDL_PROCESS_STDIO_APP:
if (!CreatePipe(stdin_pipe)) {
goto posix_spawn_fail_all;
}
if (posix_spawn_file_actions_adddup2(&fa, stdin_pipe[READ_END], STDIN_FILENO) != 0) {
SDL_SetError("posix_spawn_file_actions_adddup2 failed: %s", strerror(errno));
goto posix_spawn_fail_all;
}
break;
case SDL_PROCESS_STDIO_NULL:
if (posix_spawn_file_actions_addopen(&fa, STDIN_FILENO, "/dev/null", O_RDONLY, 0) != 0) {
SDL_SetError("posix_spawn_file_actions_addopen failed: %s", strerror(errno));
goto posix_spawn_fail_all;
}
break;
case SDL_PROCESS_STDIO_INHERITED:
default:
break;
}
switch (stdout_option) {
case SDL_PROCESS_STDIO_REDIRECT:
if (!GetStreamFD(props, SDL_PROP_PROCESS_CREATE_STDOUT_POINTER, &fd)) {
goto posix_spawn_fail_all;
}
if (posix_spawn_file_actions_adddup2(&fa, fd, STDOUT_FILENO) != 0) {
SDL_SetError("posix_spawn_file_actions_adddup2 failed: %s", strerror(errno));
goto posix_spawn_fail_all;
}
break;
case SDL_PROCESS_STDIO_APP:
if (!CreatePipe(stdout_pipe)) {
goto posix_spawn_fail_all;
}
if (posix_spawn_file_actions_adddup2(&fa, stdout_pipe[WRITE_END], STDOUT_FILENO) != 0) {
SDL_SetError("posix_spawn_file_actions_adddup2 failed: %s", strerror(errno));
goto posix_spawn_fail_all;
}
break;
case SDL_PROCESS_STDIO_NULL:
if (posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO, "/dev/null", O_WRONLY, 0644) != 0) {
SDL_SetError("posix_spawn_file_actions_addopen failed: %s", strerror(errno));
goto posix_spawn_fail_all;
}
break;
case SDL_PROCESS_STDIO_INHERITED:
default:
break;
}
if (redirect_stderr) {
if (posix_spawn_file_actions_adddup2(&fa, STDOUT_FILENO, STDERR_FILENO) != 0) {
SDL_SetError("posix_spawn_file_actions_adddup2 failed: %s", strerror(errno));
goto posix_spawn_fail_all;
}
} else {
switch (stderr_option) {
case SDL_PROCESS_STDIO_REDIRECT:
if (!GetStreamFD(props, SDL_PROP_PROCESS_CREATE_STDERR_POINTER, &fd)) {
goto posix_spawn_fail_all;
}
if (posix_spawn_file_actions_adddup2(&fa, fd, STDERR_FILENO) != 0) {
SDL_SetError("posix_spawn_file_actions_adddup2 failed: %s", strerror(errno));
goto posix_spawn_fail_all;
}
break;
case SDL_PROCESS_STDIO_APP:
if (!CreatePipe(stderr_pipe)) {
goto posix_spawn_fail_all;
}
if (posix_spawn_file_actions_adddup2(&fa, stderr_pipe[WRITE_END], STDERR_FILENO) != 0) {
SDL_SetError("posix_spawn_file_actions_adddup2 failed: %s", strerror(errno));
goto posix_spawn_fail_all;
}
break;
case SDL_PROCESS_STDIO_NULL:
if (posix_spawn_file_actions_addopen(&fa, STDERR_FILENO, "/dev/null", O_WRONLY, 0644) != 0) {
SDL_SetError("posix_spawn_file_actions_addopen failed: %s", strerror(errno));
goto posix_spawn_fail_all;
}
break;
case SDL_PROCESS_STDIO_INHERITED:
default:
break;
}
}
if (!AddFileDescriptorCloseActions(&fa)) {
goto posix_spawn_fail_all;
}
if (process->background) {
int status = -1;
#ifdef SDL_PLATFORM_APPLE
const pid_t pid = fork();
const char *forkname = "fork";
#else
const pid_t pid = vfork();
const char *forkname = "vfork";
#endif
switch (pid) {
case -1:
SDL_SetError("%s() failed: %s", forkname, strerror(errno));
goto posix_spawn_fail_all;
case 0:
setsid();
if (posix_spawnp(&data->pid, args[0], &fa, &attr, args, envp) != 0) {
_exit(errno);
}
_exit(0);
default:
if (waitpid(pid, &status, 0) < 0) {
SDL_SetError("waitpid() failed: %s", strerror(errno));
goto posix_spawn_fail_all;
}
if (status != 0) {
SDL_SetError("posix_spawn() failed: %s", strerror(status));
goto posix_spawn_fail_all;
}
break;
}
} else {
if (posix_spawnp(&data->pid, args[0], &fa, &attr, args, envp) != 0) {
SDL_SetError("posix_spawn() failed: %s", strerror(errno));
goto posix_spawn_fail_all;
}
}
SDL_SetNumberProperty(process->props, SDL_PROP_PROCESS_PID_NUMBER, data->pid);
if (stdin_option == SDL_PROCESS_STDIO_APP) {
if (!SetupStream(process, stdin_pipe[WRITE_END], "wb", SDL_PROP_PROCESS_STDIN_POINTER)) {
close(stdin_pipe[WRITE_END]);
}
close(stdin_pipe[READ_END]);
}
if (stdout_option == SDL_PROCESS_STDIO_APP) {
if (!SetupStream(process, stdout_pipe[READ_END], "rb", SDL_PROP_PROCESS_STDOUT_POINTER)) {
close(stdout_pipe[READ_END]);
}
close(stdout_pipe[WRITE_END]);
}
if (stderr_option == SDL_PROCESS_STDIO_APP) {
if (!SetupStream(process, stderr_pipe[READ_END], "rb", SDL_PROP_PROCESS_STDERR_POINTER)) {
close(stderr_pipe[READ_END]);
}
close(stderr_pipe[WRITE_END]);
}
posix_spawn_file_actions_destroy(&fa);
posix_spawnattr_destroy(&attr);
SDL_free(envp);
return true;
posix_spawn_fail_all:
posix_spawn_file_actions_destroy(&fa);
posix_spawn_fail_attr:
posix_spawnattr_destroy(&attr);
posix_spawn_fail_none:
if (stdin_pipe[READ_END] >= 0) {
close(stdin_pipe[READ_END]);
}
if (stdin_pipe[WRITE_END] >= 0) {
close(stdin_pipe[WRITE_END]);
}
if (stdout_pipe[READ_END] >= 0) {
close(stdout_pipe[READ_END]);
}
if (stdout_pipe[WRITE_END] >= 0) {
close(stdout_pipe[WRITE_END]);
}
if (stderr_pipe[READ_END] >= 0) {
close(stderr_pipe[READ_END]);
}
if (stderr_pipe[WRITE_END] >= 0) {
close(stderr_pipe[WRITE_END]);
}
SDL_free(envp);
return false;
}
bool SDL_SYS_KillProcess(SDL_Process *process, bool force)
{
int ret = kill(process->internal->pid, force ? SIGKILL : SIGTERM);
if (ret == 0) {
return true;
} else {
return SDL_SetError("Could not kill(): %s", strerror(errno));
}
}
bool SDL_SYS_WaitProcess(SDL_Process *process, bool block, int *exitcode)
{
int wstatus = 0;
int ret;
pid_t pid = process->internal->pid;
if (process->background) {
if (block) {
while (kill(pid, 0) == 0) {
SDL_Delay(10);
}
} else {
if (kill(pid, 0) == 0) {
return false;
}
}
*exitcode = 0;
return true;
} else {
ret = waitpid(pid, &wstatus, block ? 0 : WNOHANG);
if (ret < 0) {
return SDL_SetError("Could not waitpid(): %s", strerror(errno));
}
if (ret == 0) {
SDL_ClearError();
return false;
}
if (WIFEXITED(wstatus)) {
*exitcode = WEXITSTATUS(wstatus);
} else if (WIFSIGNALED(wstatus)) {
*exitcode = -WTERMSIG(wstatus);
} else {
*exitcode = -255;
}
return true;
}
}
void SDL_SYS_DestroyProcess(SDL_Process *process)
{
SDL_IOStream *io;
io = (SDL_IOStream *)SDL_GetPointerProperty(process->props, SDL_PROP_PROCESS_STDIN_POINTER, NULL);
if (io) {
SDL_CloseIO(io);
}
io = (SDL_IOStream *)SDL_GetPointerProperty(process->props, SDL_PROP_PROCESS_STDOUT_POINTER, NULL);
if (io) {
SDL_CloseIO(io);
}
io = (SDL_IOStream *)SDL_GetPointerProperty(process->props, SDL_PROP_PROCESS_STDERR_POINTER, NULL);
if (io) {
SDL_CloseIO(io);
}
SDL_free(process->internal);
}
#endif