chore(build): use SDL3

This commit is contained in:
phaneron 2025-04-12 04:38:19 -04:00
parent 9d04a35d87
commit b3c0734a9e
3286 changed files with 866354 additions and 554996 deletions

View file

@ -0,0 +1,196 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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);
}

View file

@ -0,0 +1,37 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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);

View file

@ -0,0 +1,48 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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

View file

@ -0,0 +1,483 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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 <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"
#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

View file

@ -0,0 +1,557 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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