From 76c469910eef3574dd2235684533ba68a541e1e3 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sat, 14 Sep 2024 09:13:37 -0700 Subject: [PATCH] Added SDL_PROP_PROCESS_CREATE_BACKGROUND_BOOLEAN --- include/SDL3/SDL_process.h | 12 ++- src/misc/unix/SDL_sysurl.c | 85 ++++++++++---------- src/process/SDL_process.c | 5 ++ src/process/SDL_sysprocess.h | 1 + src/process/posix/SDL_posixprocess.c | 99 +++++++++++++++++++----- src/process/windows/SDL_windowsprocess.c | 14 ++++ 6 files changed, 146 insertions(+), 70 deletions(-) diff --git a/include/SDL3/SDL_process.h b/include/SDL3/SDL_process.h index 609c4c4df..2af0bdcc9 100644 --- a/include/SDL3/SDL_process.h +++ b/include/SDL3/SDL_process.h @@ -175,6 +175,7 @@ typedef enum SDL_ProcessIO * output of the process should be redirected into the standard output of * the process. This property has no effect if * `SDL_PROP_PROCESS_CREATE_STDERR_NUMBER` is set. + * - `SDL_PROP_PROCESS_CREATE_BACKGROUND_BOOLEAN`: true if the process should run in the background. In this case the default input and output is `SDL_PROCESS_STDIO_NULL` and the exitcode of the process is not available, and will always be 0. * * On POSIX platforms, wait() and waitpid(-1, ...) should not be called, and * SIGCHLD should not be ignored or handled because those would prevent SDL @@ -208,6 +209,7 @@ extern SDL_DECLSPEC SDL_Process *SDLCALL SDL_CreateProcessWithProperties(SDL_Pro #define SDL_PROP_PROCESS_CREATE_STDERR_NUMBER "SDL.process.create.stderr_option" #define SDL_PROP_PROCESS_CREATE_STDERR_POINTER "SDL.process.create.stderr_source" #define SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN "SDL.process.create.stderr_to_stdout" +#define SDL_PROP_PROCESS_CREATE_BACKGROUND_BOOLEAN "SDL.process.create.background" /** * Get the properties associated with a process. @@ -218,6 +220,7 @@ extern SDL_DECLSPEC SDL_Process *SDLCALL SDL_CreateProcessWithProperties(SDL_Pro * - `SDL_PROP_PROCESS_STDIN_POINTER`: an SDL_IOStream that can be used to write input to the process, if it was created with `SDL_PROP_PROCESS_CREATE_STDIN_NUMBER` set to `SDL_PROCESS_STDIO_APP`. * - `SDL_PROP_PROCESS_STDOUT_POINTER`: a non-blocking SDL_IOStream that can be used to read output from the process, if it was created with `SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER` set to `SDL_PROCESS_STDIO_APP`. * - `SDL_PROP_PROCESS_STDERR_POINTER`: a non-blocking SDL_IOStream that can be used to read error output from the process, if it was created with `SDL_PROP_PROCESS_CREATE_STDERR_NUMBER` set to `SDL_PROCESS_STDIO_APP`. + * - `SDL_PROP_PROCESS_BACKGROUND_BOOLEAN`: true if the process is running in the background. * * \param process the process to query. * \returns a valid property ID on success or 0 on failure; call @@ -232,10 +235,11 @@ extern SDL_DECLSPEC SDL_Process *SDLCALL SDL_CreateProcessWithProperties(SDL_Pro */ extern SDL_DECLSPEC SDL_PropertiesID SDL_GetProcessProperties(SDL_Process *process); -#define SDL_PROP_PROCESS_PID_NUMBER "SDL.process.pid" -#define SDL_PROP_PROCESS_STDIN_POINTER "SDL.process.stdin" -#define SDL_PROP_PROCESS_STDOUT_POINTER "SDL.process.stdout" -#define SDL_PROP_PROCESS_STDERR_POINTER "SDL.process.stderr" +#define SDL_PROP_PROCESS_PID_NUMBER "SDL.process.pid" +#define SDL_PROP_PROCESS_STDIN_POINTER "SDL.process.stdin" +#define SDL_PROP_PROCESS_STDOUT_POINTER "SDL.process.stdout" +#define SDL_PROP_PROCESS_STDERR_POINTER "SDL.process.stderr" +#define SDL_PROP_PROCESS_BACKGROUND_BOOLEAN "SDL.process.background" /** * Read all the output from a process. diff --git a/src/misc/unix/SDL_sysurl.c b/src/misc/unix/SDL_sysurl.c index 681c0decc..69517f162 100644 --- a/src/misc/unix/SDL_sysurl.c +++ b/src/misc/unix/SDL_sysurl.c @@ -35,51 +35,44 @@ extern char **environ; bool SDL_SYS_OpenURL(const char *url) { - const pid_t pid1 = fork(); - if (pid1 == 0) { // child process -#ifdef USE_POSIX_SPAWN - pid_t pid2; - const char *args[] = { "xdg-open", url, NULL }; - // Clear LD_PRELOAD so Chrome opens correctly when this application is launched by Steam - SDL_unsetenv_unsafe("LD_PRELOAD"); - if (posix_spawnp(&pid2, args[0], NULL, NULL, (char **)args, environ) == 0) { - // Child process doesn't wait for possibly-blocking grandchild. - _exit(EXIT_SUCCESS); - } else { - _exit(EXIT_FAILURE); - } -#else - pid_t pid2; - // Clear LD_PRELOAD so Chrome opens correctly when this application is launched by Steam - SDL_unsetenv_unsafe("LD_PRELOAD"); - // Notice this is vfork and not fork! - pid2 = vfork(); - if (pid2 == 0) { // Grandchild process will try to launch the url - execlp("xdg-open", "xdg-open", url, NULL); - _exit(EXIT_FAILURE); - } else if (pid2 < 0) { // There was an error forking - _exit(EXIT_FAILURE); - } else { - // Child process doesn't wait for possibly-blocking grandchild. - _exit(EXIT_SUCCESS); - } -#endif // USE_POSIX_SPAWN - } else if (pid1 < 0) { - return SDL_SetError("fork() failed: %s", strerror(errno)); - } else { - int status; - if (waitpid(pid1, &status, 0) == pid1) { - if (WIFEXITED(status)) { - if (WEXITSTATUS(status) == 0) { - return true; // success! - } else { - return SDL_SetError("xdg-open reported error or failed to launch: %d", WEXITSTATUS(status)); - } - } else { - return SDL_SetError("xdg-open failed for some reason"); - } - } else { - return SDL_SetError("Waiting on xdg-open failed: %s", strerror(errno)); - } + SDL_Environment *env = NULL; + char **process_env = NULL; + const char *process_args[] = { "xdg-open", url, NULL }; + SDL_Process *process = NULL; + bool result = false; + + env = SDL_CreateEnvironment(SDL_FALSE); + if (!env) { + goto done; } + + // Clear LD_PRELOAD so Chrome opens correctly when this application is launched by Steam + SDL_UnsetEnvironmentVariable(env, "LD_PRELOAD"); + + process_env = SDL_GetEnvironmentVariables(env); + if (!process_env) { + goto done; + } + + SDL_PropertiesID props = SDL_CreateProperties(); + if (!props) { + goto done; + } + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, process_args); + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, process_env); + SDL_SetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_BACKGROUND_BOOLEAN, true); + process = SDL_CreateProcessWithProperties(props); + SDL_DestroyProperties(props); + if (!process) { + goto done; + } + + result = true; + +done: + SDL_free(process_env); + SDL_DestroyEnvironment(env); + SDL_DestroyProcess(process); + + return result; } diff --git a/src/process/SDL_process.c b/src/process/SDL_process.c index 10187e4aa..4f0ca2712 100644 --- a/src/process/SDL_process.c +++ b/src/process/SDL_process.c @@ -54,12 +54,14 @@ SDL_Process *SDL_CreateProcessWithProperties(SDL_PropertiesID props) 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); @@ -180,6 +182,9 @@ SDL_bool SDL_WaitProcess(SDL_Process *process, SDL_bool block, int *exitcode) if (SDL_SYS_WaitProcess(process, block, &process->exitcode)) { process->alive = false; if (exitcode) { + if (process->background) { + process->exitcode = 0; + } *exitcode = process->exitcode; } return true; diff --git a/src/process/SDL_sysprocess.h b/src/process/SDL_sysprocess.h index bcc8be4dc..fbea4a679 100644 --- a/src/process/SDL_sysprocess.h +++ b/src/process/SDL_sysprocess.h @@ -25,6 +25,7 @@ typedef struct SDL_ProcessData SDL_ProcessData; struct SDL_Process { bool alive; + bool background; int exitcode; SDL_PropertiesID props; SDL_ProcessData *internal; diff --git a/src/process/posix/SDL_posixprocess.c b/src/process/posix/SDL_posixprocess.c index 35cc6a5db..3aaef3cef 100644 --- a/src/process/posix/SDL_posixprocess.c +++ b/src/process/posix/SDL_posixprocess.c @@ -137,6 +137,19 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID 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)) { @@ -276,9 +289,38 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID } // Spawn the new process - if (posix_spawnp(&data->pid, args[0], &fa, &attr, args, env) != 0) { - SDL_SetError("posix_spawn failed: %s", strerror(errno)); - goto posix_spawn_fail_all; + 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, env) != 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, env) != 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); @@ -353,26 +395,43 @@ bool SDL_SYS_KillProcess(SDL_Process *process, SDL_bool force) bool SDL_SYS_WaitProcess(SDL_Process *process, SDL_bool block, int *exitcode) { int wstatus = 0; - int ret = waitpid(process->internal->pid, &wstatus, block ? 0 : WNOHANG); + int ret; + pid_t pid = process->internal->pid; - 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); + 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 { - *exitcode = -255; - } + ret = waitpid(pid, &wstatus, block ? 0 : WNOHANG); + if (ret < 0) { + return SDL_SetError("Could not waitpid(): %s", strerror(errno)); + } - return true; + 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) diff --git a/src/process/windows/SDL_windowsprocess.c b/src/process/windows/SDL_windowsprocess.c index 752933462..3820a578c 100644 --- a/src/process/windows/SDL_windowsprocess.c +++ b/src/process/windows/SDL_windowsprocess.c @@ -232,6 +232,20 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID security_attributes.bInheritHandle = TRUE; security_attributes.lpSecurityDescriptor = NULL; + // 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)) {