From 5a079a2d114f96d4847d1ee305d5b7c16eeec50e Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 27 Dec 2025 12:03:39 -0800 Subject: Initial commit --- contrib/SDL-3.2.8/src/process/SDL_process.c | 196 ++++++++ contrib/SDL-3.2.8/src/process/SDL_sysprocess.h | 37 ++ .../SDL-3.2.8/src/process/dummy/SDL_dummyprocess.c | 48 ++ .../SDL-3.2.8/src/process/posix/SDL_posixprocess.c | 483 ++++++++++++++++++ .../src/process/windows/SDL_windowsprocess.c | 557 +++++++++++++++++++++ 5 files changed, 1321 insertions(+) create mode 100644 contrib/SDL-3.2.8/src/process/SDL_process.c create mode 100644 contrib/SDL-3.2.8/src/process/SDL_sysprocess.h create mode 100644 contrib/SDL-3.2.8/src/process/dummy/SDL_dummyprocess.c create mode 100644 contrib/SDL-3.2.8/src/process/posix/SDL_posixprocess.c create mode 100644 contrib/SDL-3.2.8/src/process/windows/SDL_windowsprocess.c (limited to 'contrib/SDL-3.2.8/src/process') diff --git a/contrib/SDL-3.2.8/src/process/SDL_process.c b/contrib/SDL-3.2.8/src/process/SDL_process.c new file mode 100644 index 0000000..3ccf5d5 --- /dev/null +++ b/contrib/SDL-3.2.8/src/process/SDL_process.c @@ -0,0 +1,196 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include "SDL_sysprocess.h" + + +SDL_Process *SDL_CreateProcess(const char * const *args, bool pipe_stdio) +{ + if (!args || !args[0] || !args[0][0]) { + SDL_InvalidParamError("args"); + return NULL; + } + + SDL_Process *process; + SDL_PropertiesID props = SDL_CreateProperties(); + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (void *)args); + if (pipe_stdio) { + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_APP); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP); + } + process = SDL_CreateProcessWithProperties(props); + SDL_DestroyProperties(props); + return process; +} + +SDL_Process *SDL_CreateProcessWithProperties(SDL_PropertiesID props) +{ + const char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL); + if (!args || !args[0] || !args[0][0]) { + SDL_InvalidParamError("SDL_PROP_PROCESS_CREATE_ARGS_POINTER"); + return NULL; + } + + SDL_Process *process = (SDL_Process *)SDL_calloc(1, sizeof(*process)); + if (!process) { + return NULL; + } + process->background = SDL_GetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_BACKGROUND_BOOLEAN, false); + + process->props = SDL_CreateProperties(); + if (!process->props) { + SDL_DestroyProcess(process); + return NULL; + } + SDL_SetBooleanProperty(process->props, SDL_PROP_PROCESS_BACKGROUND_BOOLEAN, process->background); + + if (!SDL_SYS_CreateProcessWithProperties(process, props)) { + SDL_DestroyProcess(process); + return NULL; + } + process->alive = true; + return process; +} + +SDL_PropertiesID SDL_GetProcessProperties(SDL_Process *process) +{ + if (!process) { + return SDL_InvalidParamError("process"); + } + return process->props; +} + +void *SDL_ReadProcess(SDL_Process *process, size_t *datasize, int *exitcode) +{ + void *result; + + if (datasize) { + *datasize = 0; + } + if (exitcode) { + *exitcode = -1; + } + + if (!process) { + SDL_InvalidParamError("process"); + return NULL; + } + + SDL_IOStream *io = (SDL_IOStream *)SDL_GetPointerProperty(process->props, SDL_PROP_PROCESS_STDOUT_POINTER, NULL); + if (!io) { + SDL_SetError("Process not created with I/O enabled"); + return NULL; + } + + result = SDL_LoadFile_IO(io, datasize, false); + + SDL_WaitProcess(process, true, exitcode); + + return result; +} + +SDL_IOStream *SDL_GetProcessInput(SDL_Process *process) +{ + if (!process) { + SDL_InvalidParamError("process"); + return NULL; + } + + SDL_IOStream *io = (SDL_IOStream *)SDL_GetPointerProperty(process->props, SDL_PROP_PROCESS_STDIN_POINTER, NULL); + if (!io) { + SDL_SetError("Process not created with standard input available"); + return NULL; + } + + return io; +} + +SDL_IOStream *SDL_GetProcessOutput(SDL_Process *process) +{ + if (!process) { + SDL_InvalidParamError("process"); + return NULL; + } + + SDL_IOStream *io = (SDL_IOStream *)SDL_GetPointerProperty(process->props, SDL_PROP_PROCESS_STDOUT_POINTER, NULL); + if (!io) { + SDL_SetError("Process not created with standard output available"); + return NULL; + } + + return io; +} + +bool SDL_KillProcess(SDL_Process *process, bool force) +{ + if (!process) { + return SDL_InvalidParamError("process"); + } + + if (!process->alive) { + return SDL_SetError("Process isn't running"); + } + + return SDL_SYS_KillProcess(process, force); +} + +bool SDL_WaitProcess(SDL_Process *process, bool block, int *exitcode) +{ + if (!process) { + return SDL_InvalidParamError("process"); + } + + if (!process->alive) { + if (exitcode) { + *exitcode = process->exitcode; + } + return true; + } + + if (SDL_SYS_WaitProcess(process, block, &process->exitcode)) { + process->alive = false; + if (exitcode) { + if (process->background) { + process->exitcode = 0; + } + *exitcode = process->exitcode; + } + return true; + } + return false; +} + +void SDL_DestroyProcess(SDL_Process *process) +{ + if (!process) { + return; + } + + // Check to see if the process has exited, will reap zombies on POSIX platforms + if (process->alive) { + SDL_WaitProcess(process, false, NULL); + } + + SDL_SYS_DestroyProcess(process); + SDL_DestroyProperties(process->props); + SDL_free(process); +} diff --git a/contrib/SDL-3.2.8/src/process/SDL_sysprocess.h b/contrib/SDL-3.2.8/src/process/SDL_sysprocess.h new file mode 100644 index 0000000..839ef9d --- /dev/null +++ b/contrib/SDL-3.2.8/src/process/SDL_sysprocess.h @@ -0,0 +1,37 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +typedef struct SDL_ProcessData SDL_ProcessData; + +struct SDL_Process +{ + bool alive; + bool background; + int exitcode; + SDL_PropertiesID props; + SDL_ProcessData *internal; +}; + +bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID props); +bool SDL_SYS_KillProcess(SDL_Process *process, bool force); +bool SDL_SYS_WaitProcess(SDL_Process *process, bool block, int *exitcode); +void SDL_SYS_DestroyProcess(SDL_Process *process); diff --git a/contrib/SDL-3.2.8/src/process/dummy/SDL_dummyprocess.c b/contrib/SDL-3.2.8/src/process/dummy/SDL_dummyprocess.c new file mode 100644 index 0000000..ba69e4a --- /dev/null +++ b/contrib/SDL-3.2.8/src/process/dummy/SDL_dummyprocess.c @@ -0,0 +1,48 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_PROCESS_DUMMY + +#include "../SDL_sysprocess.h" + + +bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID props) +{ + return SDL_Unsupported(); +} + +bool SDL_SYS_KillProcess(SDL_Process *process, bool force) +{ + return SDL_Unsupported(); +} + +bool SDL_SYS_WaitProcess(SDL_Process *process, bool block, int *exitcode) +{ + return SDL_Unsupported(); +} + +void SDL_SYS_DestroyProcess(SDL_Process *process) +{ + return; +} + +#endif // SDL_PROCESS_DUMMY diff --git a/contrib/SDL-3.2.8/src/process/posix/SDL_posixprocess.c b/contrib/SDL-3.2.8/src/process/posix/SDL_posixprocess.c new file mode 100644 index 0000000..98b9f75 --- /dev/null +++ b/contrib/SDL-3.2.8/src/process/posix/SDL_posixprocess.c @@ -0,0 +1,483 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_PROCESS_POSIX + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../SDL_sysprocess.h" +#include "../../io/SDL_iostream_c.h" + + +#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) +{ + // Set the file descriptor to non-blocking mode + 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; + } + + // Make sure the pipe isn't accidentally inherited by another thread creating a process + 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); + + // Make sure we don't crash if we write when the pipe is closed + 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; + 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; + + // Keep the malloc() before exec() so that an OOM won't run a process at all + 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; + } + + // Background processes don't have access to the terminal + 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; + } + + // Spawn the new process + if (process->background) { + int status = -1; + #ifdef SDL_PLATFORM_APPLE // Apple has vfork marked as deprecated and (as of macOS 10.12) is almost identical to calling fork() anyhow. + 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: + // Detach from the terminal and launch the process + 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) { + // We can't wait on the status, so we'll poll to see if it's alive + 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 // SDL_PROCESS_POSIX diff --git a/contrib/SDL-3.2.8/src/process/windows/SDL_windowsprocess.c b/contrib/SDL-3.2.8/src/process/windows/SDL_windowsprocess.c new file mode 100644 index 0000000..c1aee5c --- /dev/null +++ b/contrib/SDL-3.2.8/src/process/windows/SDL_windowsprocess.c @@ -0,0 +1,557 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_PROCESS_WINDOWS + +#include "../../core/windows/SDL_windows.h" +#include "../SDL_sysprocess.h" +#include "../../io/SDL_iostream_c.h" + +#define READ_END 0 +#define WRITE_END 1 + +struct SDL_ProcessData { + PROCESS_INFORMATION process_information; +}; + +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, HANDLE handle, const char *mode, const char *property) +{ + SDL_IOStream *io = SDL_IOFromHandle(handle, mode, 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 bool SetupRedirect(SDL_PropertiesID props, const char *property, HANDLE *result) +{ + SDL_IOStream *io = (SDL_IOStream *)SDL_GetPointerProperty(props, property, NULL); + if (!io) { + SDL_SetError("%s is not set", property); + return false; + } + + HANDLE handle = (HANDLE)SDL_GetPointerProperty(SDL_GetIOProperties(io), SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER, INVALID_HANDLE_VALUE); + if (handle == INVALID_HANDLE_VALUE) { + SDL_SetError("%s doesn't have SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER available", property); + return false; + } + + if (!DuplicateHandle(GetCurrentProcess(), handle, + GetCurrentProcess(), result, + 0, TRUE, DUPLICATE_SAME_ACCESS)) { + WIN_SetError("DuplicateHandle()"); + return false; + } + + if (GetFileType(*result) == FILE_TYPE_PIPE) { + DWORD wait_mode = PIPE_WAIT; + if (!SetNamedPipeHandleState(*result, &wait_mode, NULL, NULL)) { + WIN_SetError("SetNamedPipeHandleState()"); + return false; + } + } + return true; +} + +static bool is_batch_file_path(const char *path) { + size_t len_path = SDL_strlen(path); + if (len_path < 4) { + return false; + } + if (SDL_strcasecmp(path + len_path - 4, ".bat") == 0 || SDL_strcasecmp(path + len_path - 4, ".cmd") == 0) { + return true; + } + return false; +} + +static bool join_arguments(const char * const *args, LPWSTR *args_out) +{ + size_t len; + int i; + size_t i_out; + char *result; + bool batch_file = is_batch_file_path(args[0]); + + len = 0; + for (i = 0; args[i]; i++) { + const char *a = args[i]; + + /* two double quotes to surround an argument with */ + len += 2; + + for (; *a; a++) { + switch (*a) { + case '"': + len += 2; + break; + case '\\': + /* only escape backslashes that precede a double quote */ + len += (a[1] == '"' || a[1] == '\0') ? 2 : 1; + break; + case ' ': + case '^': + case '&': + case '|': + case '<': + case '>': + if (batch_file) { + len += 2; + } else { + len += 1; + } + break; + default: + len += 1; + break; + } + } + /* space separator or final '\0' */ + len += 1; + } + + result = SDL_malloc(len); + if (!result) { + *args_out = NULL; + return false; + } + + i_out = 0; + for (i = 0; args[i]; i++) { + const char *a = args[i]; + + result[i_out++] = '"'; + for (; *a; a++) { + switch (*a) { + case '"': + if (batch_file) { + result[i_out++] = '"'; + } else { + result[i_out++] = '\\'; + } + result[i_out++] = *a; + break; + case '\\': + result[i_out++] = *a; + if (a[1] == '"' || a[1] == '\0') { + result[i_out++] = *a; + } + break; + case ' ': + if (batch_file) { + result[i_out++] = '^'; + } + result[i_out++] = *a; + break; + case '^': + case '&': + case '|': + case '<': + case '>': + if (batch_file) { + result[i_out++] = '^'; + } + result[i_out++] = *a; + break; + default: + result[i_out++] = *a; + break; + } + } + result[i_out++] = '"'; + result[i_out++] = ' '; + } + SDL_assert(i_out == len); + result[len - 1] = '\0'; + + *args_out = (LPWSTR)SDL_iconv_string("UTF-16LE", "UTF-8", (const char *)result, len); + SDL_free(result); + if (!args_out) { + return false; + } + return true; +} + +static bool join_env(char **env, LPWSTR *env_out) +{ + size_t len; + char **var; + char *result; + + len = 0; + for (var = env; *var; var++) { + len += SDL_strlen(*var) + 1; + } + result = SDL_malloc(len + 1); + if (!result) { + return false; + } + + len = 0; + for (var = env; *var; var++) { + size_t l = SDL_strlen(*var); + SDL_memcpy(result + len, *var, l); + result[len + l] = '\0'; + len += l + 1; + } + result[len] = '\0'; + + *env_out = (LPWSTR)SDL_iconv_string("UTF-16LE", "UTF-8", (const char *)result, len); + SDL_free(result); + if (!*env_out) { + return false; + } + return true; +} + +bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID props) +{ + const 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; + 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); + LPWSTR createprocess_cmdline = NULL; + LPWSTR createprocess_env = NULL; + STARTUPINFOW startup_info; + DWORD creation_flags; + SECURITY_ATTRIBUTES security_attributes; + HANDLE stdin_pipe[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE }; + HANDLE stdout_pipe[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE }; + HANDLE stderr_pipe[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE }; + DWORD pipe_mode = PIPE_NOWAIT; + bool result = false; + + // Keep the malloc() before exec() so that an OOM won't run a process at all + 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; + data->process_information.hProcess = INVALID_HANDLE_VALUE; + data->process_information.hThread = INVALID_HANDLE_VALUE; + + creation_flags = CREATE_UNICODE_ENVIRONMENT; + + SDL_zero(startup_info); + startup_info.cb = sizeof(startup_info); + startup_info.dwFlags |= STARTF_USESTDHANDLES; + startup_info.hStdInput = INVALID_HANDLE_VALUE; + startup_info.hStdOutput = INVALID_HANDLE_VALUE; + startup_info.hStdError = INVALID_HANDLE_VALUE; + + SDL_zero(security_attributes); + security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); + security_attributes.bInheritHandle = TRUE; + security_attributes.lpSecurityDescriptor = NULL; + + if (!join_arguments(args, &createprocess_cmdline)) { + goto done; + } + + if (!join_env(envp, &createprocess_env)) { + goto done; + } + + // Background processes don't have access to the terminal + // This isn't necessary on Windows, but we keep the same behavior as the POSIX implementation. + 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 (!SetupRedirect(props, SDL_PROP_PROCESS_CREATE_STDIN_POINTER, &startup_info.hStdInput)) { + goto done; + } + break; + case SDL_PROCESS_STDIO_APP: + if (!CreatePipe(&stdin_pipe[READ_END], &stdin_pipe[WRITE_END], &security_attributes, 0)) { + stdin_pipe[READ_END] = INVALID_HANDLE_VALUE; + stdin_pipe[WRITE_END] = INVALID_HANDLE_VALUE; + goto done; + } + if (!SetNamedPipeHandleState(stdin_pipe[WRITE_END], &pipe_mode, NULL, NULL)) { + WIN_SetError("SetNamedPipeHandleState()"); + goto done; + } + if (!SetHandleInformation(stdin_pipe[WRITE_END], HANDLE_FLAG_INHERIT, 0) ) { + WIN_SetError("SetHandleInformation()"); + goto done; + } + startup_info.hStdInput = stdin_pipe[READ_END]; + break; + case SDL_PROCESS_STDIO_NULL: + startup_info.hStdInput = CreateFile(TEXT("\\\\.\\NUL"), GENERIC_ALL, 0, &security_attributes, OPEN_EXISTING, 0, NULL); + break; + case SDL_PROCESS_STDIO_INHERITED: + default: + if (!DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_INPUT_HANDLE), + GetCurrentProcess(), &startup_info.hStdInput, + 0, TRUE, DUPLICATE_SAME_ACCESS)) { + startup_info.hStdInput = INVALID_HANDLE_VALUE; + WIN_SetError("DuplicateHandle()"); + goto done; + } + break; + } + + switch (stdout_option) { + case SDL_PROCESS_STDIO_REDIRECT: + if (!SetupRedirect(props, SDL_PROP_PROCESS_CREATE_STDOUT_POINTER, &startup_info.hStdOutput)) { + goto done; + } + break; + case SDL_PROCESS_STDIO_APP: + if (!CreatePipe(&stdout_pipe[READ_END], &stdout_pipe[WRITE_END], &security_attributes, 0)) { + stdout_pipe[READ_END] = INVALID_HANDLE_VALUE; + stdout_pipe[WRITE_END] = INVALID_HANDLE_VALUE; + goto done; + } + if (!SetNamedPipeHandleState(stdout_pipe[READ_END], &pipe_mode, NULL, NULL)) { + WIN_SetError("SetNamedPipeHandleState()"); + goto done; + } + if (!SetHandleInformation(stdout_pipe[READ_END], HANDLE_FLAG_INHERIT, 0) ) { + WIN_SetError("SetHandleInformation()"); + goto done; + } + startup_info.hStdOutput = stdout_pipe[WRITE_END]; + break; + case SDL_PROCESS_STDIO_NULL: + startup_info.hStdOutput = CreateFile(TEXT("\\\\.\\NUL"), GENERIC_ALL, 0, &security_attributes, OPEN_EXISTING, 0, NULL); + break; + case SDL_PROCESS_STDIO_INHERITED: + default: + if (!DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_OUTPUT_HANDLE), + GetCurrentProcess(), &startup_info.hStdOutput, + 0, TRUE, DUPLICATE_SAME_ACCESS)) { + startup_info.hStdOutput = INVALID_HANDLE_VALUE; + WIN_SetError("DuplicateHandle()"); + goto done; + } + break; + } + + if (redirect_stderr) { + if (!DuplicateHandle(GetCurrentProcess(), startup_info.hStdOutput, + GetCurrentProcess(), &startup_info.hStdError, + 0, TRUE, DUPLICATE_SAME_ACCESS)) { + startup_info.hStdError = INVALID_HANDLE_VALUE; + WIN_SetError("DuplicateHandle()"); + goto done; + } + } else { + switch (stderr_option) { + case SDL_PROCESS_STDIO_REDIRECT: + if (!SetupRedirect(props, SDL_PROP_PROCESS_CREATE_STDERR_POINTER, &startup_info.hStdError)) { + goto done; + } + break; + case SDL_PROCESS_STDIO_APP: + if (!CreatePipe(&stderr_pipe[READ_END], &stderr_pipe[WRITE_END], &security_attributes, 0)) { + stderr_pipe[READ_END] = INVALID_HANDLE_VALUE; + stderr_pipe[WRITE_END] = INVALID_HANDLE_VALUE; + goto done; + } + if (!SetNamedPipeHandleState(stderr_pipe[READ_END], &pipe_mode, NULL, NULL)) { + WIN_SetError("SetNamedPipeHandleState()"); + goto done; + } + if (!SetHandleInformation(stderr_pipe[READ_END], HANDLE_FLAG_INHERIT, 0) ) { + WIN_SetError("SetHandleInformation()"); + goto done; + } + startup_info.hStdError = stderr_pipe[WRITE_END]; + break; + case SDL_PROCESS_STDIO_NULL: + startup_info.hStdError = CreateFile(TEXT("\\\\.\\NUL"), GENERIC_ALL, 0, &security_attributes, OPEN_EXISTING, 0, NULL); + break; + case SDL_PROCESS_STDIO_INHERITED: + default: + if (!DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_ERROR_HANDLE), + GetCurrentProcess(), &startup_info.hStdError, + 0, TRUE, DUPLICATE_SAME_ACCESS)) { + startup_info.hStdError = INVALID_HANDLE_VALUE; + WIN_SetError("DuplicateHandle()"); + goto done; + } + break; + } + } + + if (!CreateProcessW(NULL, createprocess_cmdline, NULL, NULL, TRUE, creation_flags, createprocess_env, NULL, &startup_info, &data->process_information)) { + WIN_SetError("CreateProcess"); + goto done; + } + + SDL_SetNumberProperty(process->props, SDL_PROP_PROCESS_PID_NUMBER, data->process_information.dwProcessId); + + if (stdin_option == SDL_PROCESS_STDIO_APP) { + if (!SetupStream(process, stdin_pipe[WRITE_END], "wb", SDL_PROP_PROCESS_STDIN_POINTER)) { + CloseHandle(stdin_pipe[WRITE_END]); + stdin_pipe[WRITE_END] = INVALID_HANDLE_VALUE; + } + } + if (stdout_option == SDL_PROCESS_STDIO_APP) { + if (!SetupStream(process, stdout_pipe[READ_END], "rb", SDL_PROP_PROCESS_STDOUT_POINTER)) { + CloseHandle(stdout_pipe[READ_END]); + stdout_pipe[READ_END] = INVALID_HANDLE_VALUE; + } + } + if (stderr_option == SDL_PROCESS_STDIO_APP) { + if (!SetupStream(process, stderr_pipe[READ_END], "rb", SDL_PROP_PROCESS_STDERR_POINTER)) { + CloseHandle(stderr_pipe[READ_END]); + stderr_pipe[READ_END] = INVALID_HANDLE_VALUE; + } + } + + result = true; + +done: + if (startup_info.hStdInput != INVALID_HANDLE_VALUE && + startup_info.hStdInput != stdin_pipe[READ_END]) { + CloseHandle(startup_info.hStdInput); + } + if (startup_info.hStdOutput != INVALID_HANDLE_VALUE && + startup_info.hStdOutput != stdout_pipe[WRITE_END]) { + CloseHandle(startup_info.hStdOutput); + } + if (startup_info.hStdError != INVALID_HANDLE_VALUE && + startup_info.hStdError != stderr_pipe[WRITE_END]) { + CloseHandle(startup_info.hStdError); + } + if (stdin_pipe[READ_END] != INVALID_HANDLE_VALUE) { + CloseHandle(stdin_pipe[READ_END]); + } + if (stdout_pipe[WRITE_END] != INVALID_HANDLE_VALUE) { + CloseHandle(stdout_pipe[WRITE_END]); + } + if (stderr_pipe[WRITE_END] != INVALID_HANDLE_VALUE) { + CloseHandle(stderr_pipe[WRITE_END]); + } + SDL_free(createprocess_cmdline); + SDL_free(createprocess_env); + SDL_free(envp); + + if (!result) { + if (stdin_pipe[WRITE_END] != INVALID_HANDLE_VALUE) { + CloseHandle(stdin_pipe[WRITE_END]); + } + if (stdout_pipe[READ_END] != INVALID_HANDLE_VALUE) { + CloseHandle(stdout_pipe[READ_END]); + } + if (stderr_pipe[READ_END] != INVALID_HANDLE_VALUE) { + CloseHandle(stderr_pipe[READ_END]); + } + } + return result; +} + +bool SDL_SYS_KillProcess(SDL_Process *process, bool force) +{ + if (!TerminateProcess(process->internal->process_information.hProcess, 1)) { + return WIN_SetError("TerminateProcess failed"); + } + return true; +} + +bool SDL_SYS_WaitProcess(SDL_Process *process, bool block, int *exitcode) +{ + DWORD result; + + result = WaitForSingleObject(process->internal->process_information.hProcess, block ? INFINITE : 0); + + if (result == WAIT_OBJECT_0) { + DWORD rc; + if (!GetExitCodeProcess(process->internal->process_information.hProcess, &rc)) { + return WIN_SetError("GetExitCodeProcess"); + } + if (exitcode) { + *exitcode = (int)rc; + } + return true; + } else if (result == WAIT_FAILED) { + return WIN_SetError("WaitForSingleObject(hProcess) returned WAIT_FAILED"); + } else { + SDL_ClearError(); + return false; + } +} + +void SDL_SYS_DestroyProcess(SDL_Process *process) +{ + SDL_ProcessData *data = process->internal; + 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_STDERR_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); + } + if (data) { + if (data->process_information.hThread != INVALID_HANDLE_VALUE) { + CloseHandle(data->process_information.hThread); + } + if (data->process_information.hProcess != INVALID_HANDLE_VALUE) { + CloseHandle(data->process_information.hProcess); + } + } + SDL_free(data); +} + +#endif // SDL_PROCESS_WINDOWS -- cgit v1.2.3