| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518 |
- /*
- Simple DirectMedia Layer
- Copyright (C) 1997-2024 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 "../../file/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);
- FILE *fp = fdopen(fd, mode);
- if (!fp) {
- return false;
- }
- SDL_IOStream *io = SDL_IOFromFP(fp, 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 = 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;
- }
- if (!AddFileDescriptorCloseActions(&fa)) {
- goto posix_spawn_fail_all;
- }
- // 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;
- }
- if (posix_spawn_file_actions_addclose(&fa, fd) != 0) {
- SDL_SetError("posix_spawn_file_actions_addclose 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_addclose(&fa, stdin_pipe[WRITE_END]) != 0) {
- SDL_SetError("posix_spawn_file_actions_addclose failed: %s", strerror(errno));
- 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;
- }
- if (posix_spawn_file_actions_addclose(&fa, stdin_pipe[READ_END]) != 0) {
- SDL_SetError("posix_spawn_file_actions_addclose 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;
- }
- if (posix_spawn_file_actions_addclose(&fa, fd) != 0) {
- SDL_SetError("posix_spawn_file_actions_addclose 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_addclose(&fa, stdout_pipe[READ_END]) != 0) {
- SDL_SetError("posix_spawn_file_actions_addclose failed: %s", strerror(errno));
- 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;
- }
- if (posix_spawn_file_actions_addclose(&fa, stdout_pipe[WRITE_END]) != 0) {
- SDL_SetError("posix_spawn_file_actions_addclose 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;
- }
- if (posix_spawn_file_actions_addclose(&fa, fd) != 0) {
- SDL_SetError("posix_spawn_file_actions_addclose 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_addclose(&fa, stderr_pipe[READ_END]) != 0) {
- SDL_SetError("posix_spawn_file_actions_addclose failed: %s", strerror(errno));
- 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;
- }
- if (posix_spawn_file_actions_addclose(&fa, stderr_pipe[WRITE_END]) != 0) {
- SDL_SetError("posix_spawn_file_actions_addclose 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;
- }
- }
- // Spawn the new process
- if (process->background) {
- int status = -1;
- pid_t pid = vfork();
- switch (pid) {
- case -1:
- SDL_SetError("vfork() failed: %s", 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, SDL_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, SDL_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
|