From 9eea8234e67d4b12b8d83b5d129ef58fb96f34c4 Mon Sep 17 00:00:00 2001 From: Semphris Date: Thu, 29 Aug 2024 13:06:25 -0400 Subject: [PATCH] Add SDL_Process subsystem --- Android.mk | 2 + CMakeLists.txt | 53 ++ VisualC-GDK/SDL/SDL.vcxproj | 5 + VisualC-GDK/SDL/SDL.vcxproj.filters | 5 + VisualC/SDL/SDL.vcxproj | 3 + VisualC/SDL/SDL.vcxproj.filters | 11 +- Xcode/SDL/SDL.xcodeproj/project.pbxproj | 44 ++ include/SDL3/SDL.h | 1 + include/SDL3/SDL_process.h | 305 +++++++++ include/build_config/SDL_build_config.h.cmake | 5 + .../build_config/SDL_build_config_android.h | 3 + .../SDL_build_config_emscripten.h | 3 + include/build_config/SDL_build_config_ios.h | 3 + include/build_config/SDL_build_config_macos.h | 3 + .../build_config/SDL_build_config_minimal.h | 3 + include/build_config/SDL_build_config_ngage.h | 3 + .../build_config/SDL_build_config_windows.h | 3 + .../build_config/SDL_build_config_wingdk.h | 3 + include/build_config/SDL_build_config_xbox.h | 3 + src/dynapi/SDL_dynapi.sym | 8 + src/dynapi/SDL_dynapi_overrides.h | 8 + src/dynapi/SDL_dynapi_procs.h | 8 + src/process/SDL_process.c | 204 ++++++ src/process/SDL_sysprocess.h | 36 + src/process/dummy/SDL_dummyprocess.c | 48 ++ src/process/posix/SDL_posixprocess.c | 401 +++++++++++ src/process/windows/SDL_windowsprocess.c | 452 +++++++++++++ test/CMakeLists.txt | 3 + test/childprocess.c | 149 ++++ test/testprocess.c | 637 ++++++++++++++++++ 30 files changed, 2414 insertions(+), 1 deletion(-) create mode 100644 include/SDL3/SDL_process.h create mode 100644 src/process/SDL_process.c create mode 100644 src/process/SDL_sysprocess.h create mode 100644 src/process/dummy/SDL_dummyprocess.c create mode 100644 src/process/posix/SDL_posixprocess.c create mode 100644 src/process/windows/SDL_windowsprocess.c create mode 100644 test/childprocess.c create mode 100644 test/testprocess.c diff --git a/Android.mk b/Android.mk index dd034a5f9..b352cb318 100644 --- a/Android.mk +++ b/Android.mk @@ -57,6 +57,8 @@ LOCAL_SRC_FILES := \ $(wildcard $(LOCAL_PATH)/src/misc/android/*.c) \ $(wildcard $(LOCAL_PATH)/src/power/*.c) \ $(wildcard $(LOCAL_PATH)/src/power/android/*.c) \ + $(wildcard $(LOCAL_PATH)/src/process/*.c) \ + $(wildcard $(LOCAL_PATH)/src/process/dummy/*.c) \ $(wildcard $(LOCAL_PATH)/src/filesystem/*.c) \ $(wildcard $(LOCAL_PATH)/src/filesystem/android/*.c) \ $(wildcard $(LOCAL_PATH)/src/filesystem/posix/*.c) \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 9660786a5..01cc97466 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2860,6 +2860,55 @@ if (SDL_DIALOG) endif() endif() +sdl_sources("${SDL3_SOURCE_DIR}/src/process/SDL_process.c") +if(WINDOWS) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/process/windows/*.c") + set(SDL_PROCESS_WINDOWS 1) + set(HAVE_SDL_PROCESS TRUE) +else() + check_c_source_compiles(" +#include +#include + +int main(void) +{ + int pipes[2]; + int pid; + + const char * args[] = { + \"/bin/false\", + NULL + }; + + const char * env[] = { NULL }; + + pipe(pipes); + + posix_spawnattr_t attr; + posix_spawn_file_actions_t fa; + + posix_spawnattr_init(&attr); + posix_spawn_file_actions_init(&fa); + + posix_spawn_file_actions_addclose(&fa, pipes[0]); + posix_spawn_file_actions_adddup2(&fa, pipes[1], STDOUT_FILENO); + + posix_spawn(&pid, args[0], &fa, &attr, (char * const *) args, (char * const *) env); + posix_spawnp(&pid, args[0], &fa, &attr, (char * const *) args, (char * const *) env); + + posix_spawn_file_actions_destroy(&fa); + posix_spawnattr_destroy(&attr); + + return 0; +} +" HAVE_POSIX_SPAWN) + if(HAVE_POSIX_SPAWN) + sdl_glob_sources("${SDL3_SOURCE_DIR}/src/process/posix/*.c") + set(SDL_PROCESS_POSIX 1) + set(HAVE_SDL_PROCESS TRUE) + endif() +endif() + # Platform-independent options if(SDL_VIDEO) @@ -2949,6 +2998,10 @@ if(NOT HAVE_SDL_DIALOG) set(SDL_DIALOG_DUMMY 1) sdl_sources(${SDL3_SOURCE_DIR}/src/dialog/dummy/SDL_dummydialog.c) endif() +if(NOT HAVE_SDL_PROCESS) + set(SDL_PROCESS_DUMMY 1) + sdl_glob_sources(${SDL3_SOURCE_DIR}/src/process/dummy/*.c) +endif() if(NOT HAVE_CAMERA) set(SDL_CAMERA_DRIVER_DUMMY 1) sdl_glob_sources("${SDL3_SOURCE_DIR}/src/camera/dummy/*.c") diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj index 4041d532a..c6ab14168 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj +++ b/VisualC-GDK/SDL/SDL.vcxproj @@ -364,6 +364,8 @@ + + @@ -754,6 +756,9 @@ + + + CompileAsCpp diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters index 61caf6d24..017b9bafc 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -126,6 +126,9 @@ + + + @@ -283,6 +286,8 @@ + + diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index d1e3a2bb5..27883a0c4 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -284,6 +284,7 @@ + @@ -615,6 +616,8 @@ + + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index 20a50bda4..34f05bb44 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -348,6 +348,9 @@ API Headers + + API Headers + API Headers @@ -1538,10 +1541,16 @@ power - power\windows + + process + + + process\windows + + render\direct3d12 diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj index fc0651eab..23cf854b4 100644 --- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -457,6 +457,11 @@ F3B38CD7296E2E52005DA6D3 /* SDL_init.h in Headers */ = {isa = PBXBuildFile; fileRef = F3B38CCC296E2E52005DA6D3 /* SDL_init.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3B38CDB296E2E52005DA6D3 /* SDL_oldnames.h in Headers */ = {isa = PBXBuildFile; fileRef = F3B38CCD296E2E52005DA6D3 /* SDL_oldnames.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3B38CDF296E2E52005DA6D3 /* SDL_intrin.h in Headers */ = {isa = PBXBuildFile; fileRef = F3B38CCE296E2E52005DA6D3 /* SDL_intrin.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F3B439482C93595900792030 /* SDL_process.h in Headers */ = {isa = PBXBuildFile; fileRef = F3B439472C93595900792030 /* SDL_process.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F3B439512C935C2400792030 /* SDL_dummyprocess.c in Sources */ = {isa = PBXBuildFile; fileRef = F3B439502C935C2400792030 /* SDL_dummyprocess.c */; }; + F3B439532C935C2C00792030 /* SDL_posixprocess.c in Sources */ = {isa = PBXBuildFile; fileRef = F3B439522C935C2C00792030 /* SDL_posixprocess.c */; }; + F3B439562C937DAB00792030 /* SDL_process.c in Sources */ = {isa = PBXBuildFile; fileRef = F3B439542C937DAB00792030 /* SDL_process.c */; }; + F3B439572C937DAB00792030 /* SDL_sysprocess.h in Headers */ = {isa = PBXBuildFile; fileRef = F3B439552C937DAB00792030 /* SDL_sysprocess.h */; }; F3C2CB222C5DDDB2004D7998 /* SDL_categories_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3C2CB202C5DDDB2004D7998 /* SDL_categories_c.h */; }; F3C2CB232C5DDDB2004D7998 /* SDL_categories.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C2CB212C5DDDB2004D7998 /* SDL_categories.c */; }; F3D60A8328C16A1900788A3A /* SDL_hidapi_wii.c in Sources */ = {isa = PBXBuildFile; fileRef = F3D60A8228C16A1800788A3A /* SDL_hidapi_wii.c */; }; @@ -1023,6 +1028,11 @@ F3B38CCC296E2E52005DA6D3 /* SDL_init.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_init.h; path = SDL3/SDL_init.h; sourceTree = ""; }; F3B38CCD296E2E52005DA6D3 /* SDL_oldnames.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_oldnames.h; path = SDL3/SDL_oldnames.h; sourceTree = ""; }; F3B38CCE296E2E52005DA6D3 /* SDL_intrin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_intrin.h; path = SDL3/SDL_intrin.h; sourceTree = ""; }; + F3B439472C93595900792030 /* SDL_process.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_process.h; path = SDL3/SDL_process.h; sourceTree = ""; }; + F3B439502C935C2400792030 /* SDL_dummyprocess.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_dummyprocess.c; sourceTree = ""; }; + F3B439522C935C2C00792030 /* SDL_posixprocess.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_posixprocess.c; sourceTree = ""; }; + F3B439542C937DAB00792030 /* SDL_process.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_process.c; sourceTree = ""; }; + F3B439552C937DAB00792030 /* SDL_sysprocess.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_sysprocess.h; sourceTree = ""; }; F3C2CB202C5DDDB2004D7998 /* SDL_categories_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_categories_c.h; sourceTree = ""; }; F3C2CB212C5DDDB2004D7998 /* SDL_categories.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_categories.c; sourceTree = ""; }; F3D60A8228C16A1800788A3A /* SDL_hidapi_wii.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_wii.c; sourceTree = ""; }; @@ -1265,6 +1275,7 @@ F3B38CCB296E2E52005DA6D3 /* SDL_platform_defines.h */, F3F7D8AB2933074900816151 /* SDL_platform.h */, F3F7D8DB2933074D00816151 /* SDL_power.h */, + F3B439472C93595900792030 /* SDL_process.h */, F3E5A6EC2AD5E10800293D83 /* SDL_properties.h */, F3F7D8E22933074D00816151 /* SDL_rect.h */, F3F7D8DE2933074D00816151 /* SDL_render.h */, @@ -1338,6 +1349,7 @@ 000082EF09C89B62BD840000 /* main */, 5616CA47252BB278005D5928 /* misc */, A7D8A7DF23E2513F00DCD162 /* power */, + F3B439492C93597500792030 /* process */, A7D8A8DA23E2514000DCD162 /* render */, A7D8A57623E2513D00DCD162 /* sensor */, A7D8A8D223E2514000DCD162 /* stdlib */, @@ -2368,6 +2380,33 @@ path = ios; sourceTree = ""; }; + F3B439492C93597500792030 /* process */ = { + isa = PBXGroup; + children = ( + F3B4394A2C93599900792030 /* dummy */, + F3B4394B2C9359A500792030 /* posix */, + F3B439542C937DAB00792030 /* SDL_process.c */, + F3B439552C937DAB00792030 /* SDL_sysprocess.h */, + ); + path = process; + sourceTree = ""; + }; + F3B4394A2C93599900792030 /* dummy */ = { + isa = PBXGroup; + children = ( + F3B439502C935C2400792030 /* SDL_dummyprocess.c */, + ); + path = dummy; + sourceTree = ""; + }; + F3B4394B2C9359A500792030 /* posix */ = { + isa = PBXGroup; + children = ( + F3B439522C935C2C00792030 /* SDL_posixprocess.c */, + ); + path = posix; + sourceTree = ""; + }; F59C70FC00D5CB5801000001 /* pkg-support */ = { isa = PBXGroup; children = ( @@ -2442,6 +2481,7 @@ F3F7D9B92933074E00816151 /* SDL_cpuinfo.h in Headers */, A7D8B98023E2514400DCD162 /* SDL_d3dmath.h in Headers */, A7D8B8A223E2514400DCD162 /* SDL_diskaudio.h in Headers */, + F3B439482C93595900792030 /* SDL_process.h in Headers */, A7D8BB3F23E2514500DCD162 /* SDL_displayevents_c.h in Headers */, A7D8BA1923E2514400DCD162 /* SDL_draw.h in Headers */, F3C2CB222C5DDDB2004D7998 /* SDL_categories_c.h in Headers */, @@ -2572,6 +2612,7 @@ A7D8AC3F23E2514100DCD162 /* SDL_sysvideo.h in Headers */, F3F7D9792933074E00816151 /* SDL_thread.h in Headers */, A7D8B3EC23E2514300DCD162 /* SDL_thread_c.h in Headers */, + F3B439572C937DAB00792030 /* SDL_sysprocess.h in Headers */, E4F257912C81903800FCEAFC /* Metal_Blit.h in Headers */, F3F7D90D2933074E00816151 /* SDL_timer.h in Headers */, A7D8AB3123E2514100DCD162 /* SDL_timer_c.h in Headers */, @@ -2800,6 +2841,7 @@ A7D8B9F523E2514400DCD162 /* SDL_rotate.c in Sources */, A7D8BBE323E2574800DCD162 /* SDL_uikitvideo.m in Sources */, 5616CA4E252BB2A6005D5928 /* SDL_sysurl.m in Sources */, + F3B439562C937DAB00792030 /* SDL_process.c in Sources */, A7D8A97523E2514000DCD162 /* SDL_coremotionsensor.m in Sources */, F382071D284F362F004DD584 /* SDL_guid.c in Sources */, A7D8BB8D23E2514500DCD162 /* SDL_touch.c in Sources */, @@ -2862,6 +2904,7 @@ A7D8BA8B23E2514400DCD162 /* s_sin.c in Sources */, F3F528CE2C29E1C300E6CC26 /* s_modf.c in Sources */, A7D8BBEB23E2574800DCD162 /* SDL_uikitwindow.m in Sources */, + F3B439532C935C2C00792030 /* SDL_posixprocess.c in Sources */, F395BF6525633B2400942BFF /* SDL_crc32.c in Sources */, A7D8B5E723E2514300DCD162 /* SDL_power.c in Sources */, A7D8AED623E2514100DCD162 /* SDL_cocoakeyboard.m in Sources */, @@ -2905,6 +2948,7 @@ A7D8BADF23E2514500DCD162 /* e_fmod.c in Sources */, A7D8B5CF23E2514300DCD162 /* SDL_syspower.m in Sources */, A7D8BAEB23E2514500DCD162 /* e_log10.c in Sources */, + F3B439512C935C2400792030 /* SDL_dummyprocess.c in Sources */, A7D8B76423E2514300DCD162 /* SDL_mixer.c in Sources */, A7D8BB5723E2514500DCD162 /* SDL_events.c in Sources */, A7D8ADE623E2514100DCD162 /* SDL_blit_0.c in Sources */, diff --git a/include/SDL3/SDL.h b/include/SDL3/SDL.h index c9633941d..3698f6362 100644 --- a/include/SDL3/SDL.h +++ b/include/SDL3/SDL.h @@ -65,6 +65,7 @@ #include #include #include +#include #include #include #include diff --git a/include/SDL3/SDL_process.h b/include/SDL3/SDL_process.h new file mode 100644 index 000000000..fc0926333 --- /dev/null +++ b/include/SDL3/SDL_process.h @@ -0,0 +1,305 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 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. +*/ + +/** + * # CategoryProcess + * + * Process control support. + * + * These functions provide a cross-platform way to spawn and manage OS-level + * processes. + * + * You can create a new subprocess with SDL_CreateProcess() and optionally read and write to it using SDL_ReadProcess() and SDL_WriteProcess(). If more advanced functionality like chaining input between processes is necessary, you can use SDL_CreateProcessWithProperties(). + * + * You can get the status of a created process with SDL_WaitProcess(), or terminate the process with SDL_KillProcess(). + * + * Don't forget to call SDL_DestroyProcess() to clean up, whether the process + * process was killed, terminated on its own, or is still running! + */ + +#ifndef SDL_process_h_ +#define SDL_process_h_ + +#include + +#include +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SDL_Process SDL_Process; + +/** + * Create a new process. + * + * The path to the executable is supplied in args[0]. args[1..N] are additional arguments passed on the command line of the new process, and the argument list should be terminated with a NULL, e.g.: + * + * ```c + * const char *args[] = { "myprogram", "argument", NULL }; + * ``` + * + * Setting pipe_stdio to SDL_TRUE is equivalent to setting `SDL_PROP_PROCESS_CREATE_STDIN_NUMBER` and `SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER` to `SDL_PROCESS_STDIO_APP`, and will allow the use of SDL_ReadProcess() and SDL_WriteProcess(). + * + * See SDL_CreateProcessWithProperties() for more details. + * + * \param args the path and arguments for the new process. + * \param pipe_stdio SDL_TRUE to create pipes to the process's standard input and from the process's standard output, SDL_FALSE for the process to have no input and inherit the application's standard output. + * \returns the newly created and running process, or NULL if the process couldn't be created. + * + * \sa SDL_CreateProcessWithProperties + * \sa SDL_GetProcessProperties + * \sa SDL_ReadProcess + * \sa SDL_WriteProcess + * \sa SDL_KillProcess + * \sa SDL_WaitProcess + * \sa SDL_DestroyProcess + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + */ +extern SDL_DECLSPEC SDL_Process *SDLCALL SDL_CreateProcess(const char * const *args, SDL_bool pipe_stdio); + +/** + * Description of where standard I/O should be directed when creating a process. + * + * If a standard I/O stream is set to SDL_PROCESS_STDIO_INHERIT, it will go to the same place as the application's I/O stream. This is the default for standard output and standard error. + * + * If a standard I/O stream is set to SDL_PROCESS_STDIO_NULL, it is connected to `NUL:` on Windows and `/dev/null` on POSIX systems. This is the default for standard input. + * + * If a standard I/O stream is set to SDL_PROCESS_STDIO_APP, it is connected to a new SDL_IOStream that is available to the application. Standard input will be available as `SDL_PROP_PROCESS_STDIN_POINTER` and allows SDL_WriteProcess(), standard output will be available as `SDL_PROP_PROCESS_STDOUT_POINTER` and allows SDL_ReadProcess(), and standard error will be available as `SDL_PROP_PROCESS_STDERR_POINTER` in the properties for the created process. + * + * If a standard I/O stream is set to SDL_PROCESS_STDIO_REDIRECT, it is connected to an existing SDL_IOStream provided by the application. Standard input is provided using `SDL_PROP_PROCESS_CREATE_STDIN_POINTER`, standard output is provided using `SDL_PROP_PROCESS_CREATE_STDOUT_POINTER`, and standard error is provided using `SDL_PROP_PROCESS_CREATE_STDERR_POINTER` in the creation properties. These existing streams should be closed by the application once the new process is created. + * + * In order to use an SDL_IOStream with SDL_PROCESS_STDIO_REDIRECT, it must have `SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER` or `SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER` set. This is true for streams representing files and process I/O. + * + * \sa SDL_CreateProcessWithProperties + * \sa SDL_GetProcessProperties + * \sa SDL_ReadProcess + * \sa SDL_WriteProcess + * + * \since This enum is available since SDL 3.0.0. + */ +typedef enum SDL_ProcessIO +{ + SDL_PROCESS_STDIO_INHERITED, /**< The I/O stream is inherited from the application. */ + SDL_PROCESS_STDIO_NULL, /**< The I/O stream is ignored. */ + SDL_PROCESS_STDIO_APP, /**< The I/O stream is connected to a new SDL_IOStream that the application can read or write */ + SDL_PROCESS_STDIO_REDIRECT, /**< The I/O stream is redirected to an existing SDL_IOStream. */ +} SDL_ProcessIO; + +/** + * Create a new process with the specified properties. + * + * These are the supported properties: + * + * - `SDL_PROP_PROCESS_CREATE_ARGS_POINTER`: an array of strings containing the program to run, any arguments, and a NULL pointer, e.g. const char *args[] = { "myprogram", "argument", NULL }. This is a required property. + * - `SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER`: an array of strings containing variable=value, and a NULL pointer, e.g. const char *env[] = { "PATH=/bin:/usr/bin", NULL }. If this property is set, it will be the entire environment for the process, otherwise the current environment is used. + * - `SDL_PROP_PROCESS_CREATE_STDIN_NUMBER`: an SDL_ProcessIO value describing where standard input for the process comes from, defaults to `SDL_PROCESS_STDIO_NULL`. + * - `SDL_PROP_PROCESS_CREATE_STDIN_POINTER`: an SDL_IOStream pointer used for standard input when `SDL_PROP_PROCESS_CREATE_STDIN_NUMBER` is set to `SDL_PROCESS_STDIO_REDIRECT`. + * - `SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER`: an SDL_ProcessIO value describing where standard output for the process goes go, defaults to `SDL_PROCESS_STDIO_INHERITED`. + * - `SDL_PROP_PROCESS_CREATE_STDOUT_POINTER`: an SDL_IOStream pointer used for standard output when `SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER` is set to `SDL_PROCESS_STDIO_REDIRECT`. + * - `SDL_PROP_PROCESS_CREATE_STDERR_NUMBER`: an SDL_ProcessIO value describing where standard error for the process goes go, defaults to `SDL_PROCESS_STDIO_INHERITED`. + * - `SDL_PROP_PROCESS_CREATE_STDERR_POINTER`: an SDL_IOStream pointer used for standard error when `SDL_PROP_PROCESS_CREATE_STDERR_NUMBER` is set to `SDL_PROCESS_STDIO_REDIRECT`. + * - `SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN`: true if the error 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. + * + * On POSIX platforms, wait() and waitpid(-1, ...) should not be called, and SIGCHLD should not be ignored or handled because those would prevent SDL from properly tracking the lifetime of the underlying process. You should use SDL_WaitProcess() instead. + * + * \param props the properties to use. + * \returns the newly created and running process, or NULL if the process couldn't be created. + * + * \sa SDL_CreateProcess + * \sa SDL_GetProcessProperties + * \sa SDL_ReadProcess + * \sa SDL_WriteProcess + * \sa SDL_KillProcess + * \sa SDL_WaitProcess + * \sa SDL_DestroyProcess + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + */ +extern SDL_DECLSPEC SDL_Process *SDLCALL SDL_CreateProcessWithProperties(SDL_PropertiesID props); + +#define SDL_PROP_PROCESS_CREATE_ARGS_POINTER "SDL.process.create.args" +#define SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER "SDL.process.create.environment" +#define SDL_PROP_PROCESS_CREATE_STDIN_NUMBER "SDL.process.create.stdin_option" +#define SDL_PROP_PROCESS_CREATE_STDIN_POINTER "SDL.process.create.stdin_source" +#define SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER "SDL.process.create.stdout_option" +#define SDL_PROP_PROCESS_CREATE_STDOUT_POINTER "SDL.process.create.stdout_source" +#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" + +/** + * Get the properties associated with a process. + * + * The following read-only properties are provided by SDL: + * + * - `SDL_PROP_PROCESS_PID_NUMBER`: the process ID of the process. + * - `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`. + * + * \param process the process to query. + * \returns a valid property ID on success or 0 on failure; call + * SDL_GetError() for more information. + * + * \sa SDL_CreateProcess + * \sa SDL_CreateProcessWithProperties + * + * \threadsafety It is safe to call this function from any thread. + * + * \since This function is available since SDL 3.0.0. + */ +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" + +/** + * Read all the output from a process. + * + * If a process was created with I/O enabled, you can use this function to read the output. This function blocks until the process is complete, capturing all output, and providing the process exit code. + * + * This is just a convenience function. If you need more control over the process, you can get the output stream from the process properties and read it directly. + * + * The data is allocated with a zero byte at the end (null terminated) for + * convenience. This extra byte is not included in the value reported via + * `datasize`. + * + * The data should be freed with SDL_free(). + * + * \param process The process to read. + * \param datasize a pointer filled in with the number of bytes read, may be NULL. + * \param exitcode a pointer filled in with the process exit code if the process has exited, may be NULL. + * \returns the data or NULL on failure; call SDL_GetError() for more + * information. + * + * \sa SDL_CreateProcess + * \sa SDL_CreateProcessWithProperties + * \sa SDL_GetProcessProperties + * \sa SDL_WriteProcess + * \sa SDL_DestroyProcess + * + * \threadsafety This function is not thread safe. + * + * \since This function is available since SDL 3.0.0. + */ +extern SDL_DECLSPEC void * SDLCALL SDL_ReadProcess(SDL_Process *process, size_t *datasize, int *exitcode); + +/** + * Write to a process. + * + * If a process was created with I/O enabled, you can use this function to send data as input to the process. This function blocks until the data is written. + * + * This is just a convenience function. If the process is structured so it takes large amounts of input and generates lots of output, you should get the input and output streams from the process properties and handle them simultaneously to prevent the process from being blocked waiting for I/O. + * + * \param process The process to write. + * \param ptr a pointer to a buffer containing data to write. + * \param size the number of bytes to write. + * \param closeio if SDL_TRUE, closes the process input before returning, + * even in the case of an error. + * \returns SDL_TRUE on success or SDL_FALSE on failure; call SDL_GetError() + * for more information. + * + * \sa SDL_CreateProcess + * \sa SDL_CreateProcessWithProperties + * \sa SDL_GetProcessProperties + * \sa SDL_ReadProcess + * \sa SDL_DestroyProcess + * + * \threadsafety This function is not thread safe. + * + * \since This function is available since SDL 3.0.0. + */ +extern SDL_DECLSPEC SDL_bool SDLCALL SDL_WriteProcess(SDL_Process *process, const void *ptr, size_t size, SDL_bool closeio); + +/** + * Stop a process. + * + * \param process The process to stop. + * \param force SDL_TRUE to terminate the process immediately, SDL_FALSE to try to stop the process gracefully. In general you should try to stop the process gracefully first as terminating a process may leave it with half-written data or in some other unstable state. + * \returns SDL_TRUE on success or SDL_FALSE on failure; call SDL_GetError() + * for more information. + * + * \sa SDL_CreateProcess + * \sa SDL_CreateProcessWithProperties + * \sa SDL_WaitProcess + * \sa SDL_DestroyProcess + * + * \threadsafety This function is not thread safe. + * + * \since This function is available since SDL 3.0.0. + */ +extern SDL_DECLSPEC SDL_bool SDLCALL SDL_KillProcess(SDL_Process *process, SDL_bool force); + +/** + * Wait for a process to finish. + * + * This can be called multiple times to get the status of a process. + * + * The exit code will be the exit code of the process if it terminates normally, a negative signal if it terminated due to a signal, or -255 otherwise. It will not be changed if the process is still running. + * + * \param process The process to wait for. + * \param block If true, block until the process finishes; otherwise, report on the process' status. + * \param exitcode a pointer filled in with the process exit code if the process has exited, may be NULL. + * \returns SDL_TRUE if the process exited, SDL_FALSE otherwise. + * + * \sa SDL_CreateProcess + * \sa SDL_CreateProcessWithProperties + * \sa SDL_KillProcess + * \sa SDL_DestroyProcess + * + * \threadsafety This function is not thread safe. + * + * \since This function is available since SDL 3.0.0. + */ +extern SDL_DECLSPEC SDL_bool SDLCALL SDL_WaitProcess(SDL_Process *process, SDL_bool block, int *exitcode); + +/** + * Destroy a previously created process object. + * + * Note that this does not stop the process, just destroys the SDL object used to track it. If you want to stop the process you should use SDL_KillProcess(). + * + * \param process The process object to destroy. + * + * \sa SDL_CreateProcess + * \sa SDL_CreateProcessWithProperties + * \sa SDL_KillProcess + * + * \threadsafety This function is not thread safe. + * + * \since This function is available since SDL 3.0.0. + */ +extern SDL_DECLSPEC void SDLCALL SDL_DestroyProcess(SDL_Process *process); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include + +#endif /* SDL_process_h_ */ diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index d4a96f115..d0598aea8 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -326,6 +326,11 @@ #cmakedefine SDL_LIBUSB_DYNAMIC @SDL_LIBUSB_DYNAMIC@ #cmakedefine SDL_UDEV_DYNAMIC @SDL_UDEV_DYNAMIC@ +/* Enable various process implementations */ +#cmakedefine SDL_PROCESS_DUMMY @SDL_PROCESS_DUMMY@ +#cmakedefine SDL_PROCESS_POSIX @SDL_PROCESS_POSIX@ +#cmakedefine SDL_PROCESS_WINDOWS @SDL_PROCESS_WINDOWS@ + /* Enable various sensor drivers */ #cmakedefine SDL_SENSOR_ANDROID @SDL_SENSOR_ANDROID@ #cmakedefine SDL_SENSOR_COREMOTION @SDL_SENSOR_COREMOTION@ diff --git a/include/build_config/SDL_build_config_android.h b/include/build_config/SDL_build_config_android.h index ee567986f..db5e3cebd 100644 --- a/include/build_config/SDL_build_config_android.h +++ b/include/build_config/SDL_build_config_android.h @@ -159,6 +159,9 @@ #define SDL_HAPTIC_ANDROID 1 #endif /* SDL_HAPTIC_DISABLED */ +/* Enable the stub process support */ +#define SDL_PROCESS_DUMMY 1 + /* Enable sensor driver */ #ifndef SDL_SENSOR_DISABLED #define SDL_SENSOR_ANDROID 1 diff --git a/include/build_config/SDL_build_config_emscripten.h b/include/build_config/SDL_build_config_emscripten.h index e4ac92abf..d19cf8856 100644 --- a/include/build_config/SDL_build_config_emscripten.h +++ b/include/build_config/SDL_build_config_emscripten.h @@ -166,6 +166,9 @@ #define SDL_JOYSTICK_EMSCRIPTEN 1 #define SDL_JOYSTICK_VIRTUAL 1 +/* Enable various process implementations */ +#define SDL_PROCESS_DUMMY 1 + /* Enable various sensor drivers */ #define SDL_SENSOR_DUMMY 1 diff --git a/include/build_config/SDL_build_config_ios.h b/include/build_config/SDL_build_config_ios.h index fe87d84e3..738a81752 100644 --- a/include/build_config/SDL_build_config_ios.h +++ b/include/build_config/SDL_build_config_ios.h @@ -152,6 +152,9 @@ #define SDL_JOYSTICK_MFI 1 #define SDL_JOYSTICK_VIRTUAL 1 +/* Enable various process implementations */ +#define SDL_PROCESS_DUMMY 1 + #ifdef SDL_PLATFORM_TVOS #define SDL_SENSOR_DUMMY 1 #else diff --git a/include/build_config/SDL_build_config_macos.h b/include/build_config/SDL_build_config_macos.h index 40a8895d7..cd041b9e9 100644 --- a/include/build_config/SDL_build_config_macos.h +++ b/include/build_config/SDL_build_config_macos.h @@ -170,6 +170,9 @@ #define SDL_JOYSTICK_MFI 1 #endif +/* Enable various process implementations */ +#define SDL_PROCESS_POSIX 1 + /* Enable the dummy sensor driver */ #define SDL_SENSOR_DUMMY 1 diff --git a/include/build_config/SDL_build_config_minimal.h b/include/build_config/SDL_build_config_minimal.h index f949f94f6..5dc4305a9 100644 --- a/include/build_config/SDL_build_config_minimal.h +++ b/include/build_config/SDL_build_config_minimal.h @@ -74,6 +74,9 @@ typedef unsigned int uintptr_t; /* Enable the stub HIDAPI */ #define SDL_HIDAPI_DISABLED 1 +/* Enable the stub process support */ +#define SDL_PROCESS_DUMMY 1 + /* Enable the stub sensor driver (src/sensor/dummy/\*.c) */ #define SDL_SENSOR_DISABLED 1 diff --git a/include/build_config/SDL_build_config_ngage.h b/include/build_config/SDL_build_config_ngage.h index 596943703..a834bc09f 100644 --- a/include/build_config/SDL_build_config_ngage.h +++ b/include/build_config/SDL_build_config_ngage.h @@ -77,6 +77,9 @@ typedef unsigned long uintptr_t; /* Enable the stub HIDAPI */ #define SDL_HIDAPI_DISABLED 1 +/* Enable the stub process support */ +#define SDL_PROCESS_DUMMY 1 + /* Enable the stub sensor driver (src/sensor/dummy/\*.c) */ #define SDL_SENSOR_DISABLED 1 diff --git a/include/build_config/SDL_build_config_windows.h b/include/build_config/SDL_build_config_windows.h index a64d1d2dd..24e21f88f 100644 --- a/include/build_config/SDL_build_config_windows.h +++ b/include/build_config/SDL_build_config_windows.h @@ -250,6 +250,9 @@ typedef unsigned int uintptr_t; #define SDL_JOYSTICK_XINPUT 1 #define SDL_HAPTIC_DINPUT 1 +/* Enable various process implementations */ +#define SDL_PROCESS_WINDOWS 1 + /* Enable the sensor driver */ #ifdef HAVE_SENSORSAPI_H #define SDL_SENSOR_WINDOWS 1 diff --git a/include/build_config/SDL_build_config_wingdk.h b/include/build_config/SDL_build_config_wingdk.h index 061b44133..5b7ebf1dd 100644 --- a/include/build_config/SDL_build_config_wingdk.h +++ b/include/build_config/SDL_build_config_wingdk.h @@ -174,6 +174,9 @@ #define SDL_JOYSTICK_XINPUT 1 #define SDL_HAPTIC_DINPUT 1 +/* Enable various process implementations */ +#define SDL_PROCESS_WINDOWS 1 + /* Enable the sensor driver */ #ifdef HAVE_SENSORSAPI_H #define SDL_SENSOR_WINDOWS 1 diff --git a/include/build_config/SDL_build_config_xbox.h b/include/build_config/SDL_build_config_xbox.h index ae80b5ef6..02e06799e 100644 --- a/include/build_config/SDL_build_config_xbox.h +++ b/include/build_config/SDL_build_config_xbox.h @@ -172,6 +172,9 @@ #define SDL_JOYSTICK_GAMEINPUT 1 #define SDL_HAPTIC_DUMMY 1 +/* Enable various process implementations */ +#define SDL_PROCESS_DUMMY 1 + /* Enable the sensor driver */ #ifdef HAVE_SENSORSAPI_H #define SDL_SENSOR_WINDOWS 1 diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 2b906b96d..5f21c4740 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -97,6 +97,8 @@ SDL3_0.0.0 { SDL_CreateMutex; SDL_CreatePalette; SDL_CreatePopupWindow; + SDL_CreateProcess; + SDL_CreateProcessWithProperties; SDL_CreateProperties; SDL_CreateRWLock; SDL_CreateRenderer; @@ -127,6 +129,7 @@ SDL3_0.0.0 { SDL_DestroyHapticEffect; SDL_DestroyMutex; SDL_DestroyPalette; + SDL_DestroyProcess; SDL_DestroyProperties; SDL_DestroyRWLock; SDL_DestroyRenderer; @@ -420,6 +423,7 @@ SDL3_0.0.0 { SDL_GetPreferredLocales; SDL_GetPrimaryDisplay; SDL_GetPrimarySelectionText; + SDL_GetProcessProperties; SDL_GetPropertyType; SDL_GetRGB; SDL_GetRGBA; @@ -590,6 +594,7 @@ SDL3_0.0.0 { SDL_IsTablet; SDL_JoystickConnected; SDL_JoystickEventsEnabled; + SDL_KillProcess; SDL_LoadBMP; SDL_LoadBMP_IO; SDL_LoadFile; @@ -676,6 +681,7 @@ SDL3_0.0.0 { SDL_QuitSubSystem; SDL_RaiseWindow; SDL_ReadIO; + SDL_ReadProcess; SDL_ReadS16BE; SDL_ReadS16LE; SDL_ReadS32BE; @@ -951,6 +957,7 @@ SDL3_0.0.0 { SDL_WaitEventTimeout; SDL_WaitForGPUFences; SDL_WaitForGPUIdle; + SDL_WaitProcess; SDL_WaitSemaphore; SDL_WaitSemaphoreTimeout; SDL_WaitThread; @@ -961,6 +968,7 @@ SDL3_0.0.0 { SDL_WindowSupportsGPUPresentMode; SDL_WindowSupportsGPUSwapchainComposition; SDL_WriteIO; + SDL_WriteProcess; SDL_WriteS16BE; SDL_WriteS16LE; SDL_WriteS32BE; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 212ccf4de..8b4ebc54f 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -122,6 +122,8 @@ #define SDL_CreateMutex SDL_CreateMutex_REAL #define SDL_CreatePalette SDL_CreatePalette_REAL #define SDL_CreatePopupWindow SDL_CreatePopupWindow_REAL +#define SDL_CreateProcess SDL_CreateProcess_REAL +#define SDL_CreateProcessWithProperties SDL_CreateProcessWithProperties_REAL #define SDL_CreateProperties SDL_CreateProperties_REAL #define SDL_CreateRWLock SDL_CreateRWLock_REAL #define SDL_CreateRenderer SDL_CreateRenderer_REAL @@ -152,6 +154,7 @@ #define SDL_DestroyHapticEffect SDL_DestroyHapticEffect_REAL #define SDL_DestroyMutex SDL_DestroyMutex_REAL #define SDL_DestroyPalette SDL_DestroyPalette_REAL +#define SDL_DestroyProcess SDL_DestroyProcess_REAL #define SDL_DestroyProperties SDL_DestroyProperties_REAL #define SDL_DestroyRWLock SDL_DestroyRWLock_REAL #define SDL_DestroyRenderer SDL_DestroyRenderer_REAL @@ -445,6 +448,7 @@ #define SDL_GetPreferredLocales SDL_GetPreferredLocales_REAL #define SDL_GetPrimaryDisplay SDL_GetPrimaryDisplay_REAL #define SDL_GetPrimarySelectionText SDL_GetPrimarySelectionText_REAL +#define SDL_GetProcessProperties SDL_GetProcessProperties_REAL #define SDL_GetPropertyType SDL_GetPropertyType_REAL #define SDL_GetRGB SDL_GetRGB_REAL #define SDL_GetRGBA SDL_GetRGBA_REAL @@ -615,6 +619,7 @@ #define SDL_IsTablet SDL_IsTablet_REAL #define SDL_JoystickConnected SDL_JoystickConnected_REAL #define SDL_JoystickEventsEnabled SDL_JoystickEventsEnabled_REAL +#define SDL_KillProcess SDL_KillProcess_REAL #define SDL_LoadBMP SDL_LoadBMP_REAL #define SDL_LoadBMP_IO SDL_LoadBMP_IO_REAL #define SDL_LoadFile SDL_LoadFile_REAL @@ -701,6 +706,7 @@ #define SDL_QuitSubSystem SDL_QuitSubSystem_REAL #define SDL_RaiseWindow SDL_RaiseWindow_REAL #define SDL_ReadIO SDL_ReadIO_REAL +#define SDL_ReadProcess SDL_ReadProcess_REAL #define SDL_ReadS16BE SDL_ReadS16BE_REAL #define SDL_ReadS16LE SDL_ReadS16LE_REAL #define SDL_ReadS32BE SDL_ReadS32BE_REAL @@ -976,6 +982,7 @@ #define SDL_WaitEventTimeout SDL_WaitEventTimeout_REAL #define SDL_WaitForGPUFences SDL_WaitForGPUFences_REAL #define SDL_WaitForGPUIdle SDL_WaitForGPUIdle_REAL +#define SDL_WaitProcess SDL_WaitProcess_REAL #define SDL_WaitSemaphore SDL_WaitSemaphore_REAL #define SDL_WaitSemaphoreTimeout SDL_WaitSemaphoreTimeout_REAL #define SDL_WaitThread SDL_WaitThread_REAL @@ -986,6 +993,7 @@ #define SDL_WindowSupportsGPUPresentMode SDL_WindowSupportsGPUPresentMode_REAL #define SDL_WindowSupportsGPUSwapchainComposition SDL_WindowSupportsGPUSwapchainComposition_REAL #define SDL_WriteIO SDL_WriteIO_REAL +#define SDL_WriteProcess SDL_WriteProcess_REAL #define SDL_WriteS16BE SDL_WriteS16BE_REAL #define SDL_WriteS16LE SDL_WriteS16LE_REAL #define SDL_WriteS32BE SDL_WriteS32BE_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index fad787ac3..6c32801ca 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -142,6 +142,8 @@ SDL_DYNAPI_PROC(int,SDL_CreateHapticEffect,(SDL_Haptic *a, const SDL_HapticEffec SDL_DYNAPI_PROC(SDL_Mutex*,SDL_CreateMutex,(void),(),return) SDL_DYNAPI_PROC(SDL_Palette*,SDL_CreatePalette,(int a),(a),return) SDL_DYNAPI_PROC(SDL_Window*,SDL_CreatePopupWindow,(SDL_Window *a, int b, int c, int d, int e, SDL_WindowFlags f),(a,b,c,d,e,f),return) +SDL_DYNAPI_PROC(SDL_Process*,SDL_CreateProcess,(const char * const *a, SDL_bool b),(a,b),return) +SDL_DYNAPI_PROC(SDL_Process*,SDL_CreateProcessWithProperties,(SDL_PropertiesID a),(a),return) SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_CreateProperties,(void),(),return) SDL_DYNAPI_PROC(SDL_RWLock*,SDL_CreateRWLock,(void),(),return) SDL_DYNAPI_PROC(SDL_Renderer*,SDL_CreateRenderer,(SDL_Window *a, const char *b),(a,b),return) @@ -172,6 +174,7 @@ SDL_DYNAPI_PROC(void,SDL_DestroyGPUDevice,(SDL_GPUDevice *a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyHapticEffect,(SDL_Haptic *a, int b),(a,b),) SDL_DYNAPI_PROC(void,SDL_DestroyMutex,(SDL_Mutex *a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyPalette,(SDL_Palette *a),(a),) +SDL_DYNAPI_PROC(void,SDL_DestroyProcess,(SDL_Process *a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyProperties,(SDL_PropertiesID a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyRWLock,(SDL_RWLock *a),(a),) SDL_DYNAPI_PROC(void,SDL_DestroyRenderer,(SDL_Renderer *a),(a),) @@ -465,6 +468,7 @@ SDL_DYNAPI_PROC(char*,SDL_GetPrefPath,(const char *a, const char *b),(a,b),retur SDL_DYNAPI_PROC(SDL_Locale**,SDL_GetPreferredLocales,(int *a),(a),return) SDL_DYNAPI_PROC(SDL_DisplayID,SDL_GetPrimaryDisplay,(void),(),return) SDL_DYNAPI_PROC(char*,SDL_GetPrimarySelectionText,(void),(),return) +SDL_DYNAPI_PROC(SDL_PropertiesID,SDL_GetProcessProperties,(SDL_Process *a),(a),return) SDL_DYNAPI_PROC(SDL_PropertyType,SDL_GetPropertyType,(SDL_PropertiesID a, const char *b),(a,b),return) SDL_DYNAPI_PROC(void,SDL_GetRGB,(Uint32 a, const SDL_PixelFormatDetails *b, const SDL_Palette *c, Uint8 *d, Uint8 *e, Uint8 *f),(a,b,c,d,e,f),) SDL_DYNAPI_PROC(void,SDL_GetRGBA,(Uint32 a, const SDL_PixelFormatDetails *b, const SDL_Palette *c, Uint8 *d, Uint8 *e, Uint8 *f, Uint8 *g),(a,b,c,d,e,f,g),) @@ -634,6 +638,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_IsMouseHaptic,(void),(),return) SDL_DYNAPI_PROC(SDL_bool,SDL_IsTablet,(void),(),return) SDL_DYNAPI_PROC(SDL_bool,SDL_JoystickConnected,(SDL_Joystick *a),(a),return) SDL_DYNAPI_PROC(SDL_bool,SDL_JoystickEventsEnabled,(void),(),return) +SDL_DYNAPI_PROC(SDL_bool,SDL_KillProcess,(SDL_Process *a, SDL_bool b),(a,b),return) SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadBMP,(const char *a),(a),return) SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadBMP_IO,(SDL_IOStream *a, SDL_bool b),(a,b),return) SDL_DYNAPI_PROC(void*,SDL_LoadFile,(const char *a, size_t *b),(a,b),return) @@ -712,6 +717,7 @@ SDL_DYNAPI_PROC(void,SDL_Quit,(void),(),) SDL_DYNAPI_PROC(void,SDL_QuitSubSystem,(SDL_InitFlags a),(a),) SDL_DYNAPI_PROC(SDL_bool,SDL_RaiseWindow,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(size_t,SDL_ReadIO,(SDL_IOStream *a, void *b, size_t c),(a,b,c),return) +SDL_DYNAPI_PROC(void*,SDL_ReadProcess,(SDL_Process *a, size_t *b, int *c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_bool,SDL_ReadS16BE,(SDL_IOStream *a, Sint16 *b),(a,b),return) SDL_DYNAPI_PROC(SDL_bool,SDL_ReadS16LE,(SDL_IOStream *a, Sint16 *b),(a,b),return) SDL_DYNAPI_PROC(SDL_bool,SDL_ReadS32BE,(SDL_IOStream *a, Sint32 *b),(a,b),return) @@ -986,6 +992,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_WaitEvent,(SDL_Event *a),(a),return) SDL_DYNAPI_PROC(SDL_bool,SDL_WaitEventTimeout,(SDL_Event *a, Sint32 b),(a,b),return) SDL_DYNAPI_PROC(void,SDL_WaitForGPUFences,(SDL_GPUDevice *a, SDL_bool b, SDL_GPUFence *const *c, Uint32 d),(a,b,c,d),) SDL_DYNAPI_PROC(void,SDL_WaitForGPUIdle,(SDL_GPUDevice *a),(a),) +SDL_DYNAPI_PROC(SDL_bool,SDL_WaitProcess,(SDL_Process *a, SDL_bool b, int *c),(a,b,c),return) SDL_DYNAPI_PROC(void,SDL_WaitSemaphore,(SDL_Semaphore *a),(a),) SDL_DYNAPI_PROC(SDL_bool,SDL_WaitSemaphoreTimeout,(SDL_Semaphore *a, Sint32 b),(a,b),return) SDL_DYNAPI_PROC(void,SDL_WaitThread,(SDL_Thread *a, int *b),(a,b),) @@ -996,6 +1003,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_WindowHasSurface,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(SDL_bool,SDL_WindowSupportsGPUPresentMode,(SDL_GPUDevice *a, SDL_Window *b, SDL_GPUPresentMode c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_bool,SDL_WindowSupportsGPUSwapchainComposition,(SDL_GPUDevice *a, SDL_Window *b, SDL_GPUSwapchainComposition c),(a,b,c),return) SDL_DYNAPI_PROC(size_t,SDL_WriteIO,(SDL_IOStream *a, const void *b, size_t c),(a,b,c),return) +SDL_DYNAPI_PROC(SDL_bool,SDL_WriteProcess,(SDL_Process *a, const void *b, size_t c, SDL_bool d),(a,b,c,d),return) SDL_DYNAPI_PROC(SDL_bool,SDL_WriteS16BE,(SDL_IOStream *a, Sint16 b),(a,b),return) SDL_DYNAPI_PROC(SDL_bool,SDL_WriteS16LE,(SDL_IOStream *a, Sint16 b),(a,b),return) SDL_DYNAPI_PROC(SDL_bool,SDL_WriteS32BE,(SDL_IOStream *a, Sint32 b),(a,b),return) diff --git a/src/process/SDL_process.c b/src/process/SDL_process.c new file mode 100644 index 000000000..10187e4aa --- /dev/null +++ b/src/process/SDL_process.c @@ -0,0 +1,204 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 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, SDL_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->props = SDL_CreateProperties(); + if (!process->props) { + SDL_DestroyProcess(process); + return NULL; + } + + 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_bool SDL_WriteProcess(SDL_Process *process, const void *ptr, size_t size, SDL_bool closeio) +{ + bool result = false; + SDL_IOStream *io = NULL; + + if (!process) { + SDL_InvalidParamError("process"); + goto done; + } + + io = (SDL_IOStream *)SDL_GetPointerProperty(process->props, SDL_PROP_PROCESS_STDIN_POINTER, NULL); + if (!io) { + SDL_SetError("Process not created with I/O enabled"); + goto done; + } + + size_t written = 0, left = size; + while (left > 0) { + size_t amount = SDL_WriteIO(io, (Uint8 *)ptr + written, left); + if (amount > 0) { + written += amount; + left -= amount; + continue; + } else if (SDL_GetIOStatus(io) == SDL_IO_STATUS_NOT_READY) { + // Wait for the stream to be ready + SDL_Delay(1); + continue; + } + + // The stream status will remain set for the caller to check + break; + } + + result = (written == size); + +done: + if (result) { + result = SDL_FlushIO(io); + } + if (closeio) { + result &= SDL_CloseIO(io); + } + return result; +} + +SDL_bool SDL_KillProcess(SDL_Process *process, SDL_bool force) +{ + if (!process) { + return SDL_InvalidParamError("process"); + } + + if (!process->alive) { + return SDL_SetError("Process isn't running"); + } + + return SDL_SYS_KillProcess(process, force); +} + +SDL_bool SDL_WaitProcess(SDL_Process *process, SDL_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) { + *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/src/process/SDL_sysprocess.h b/src/process/SDL_sysprocess.h new file mode 100644 index 000000000..bcc8be4dc --- /dev/null +++ b/src/process/SDL_sysprocess.h @@ -0,0 +1,36 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 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; + 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, SDL_bool force); +bool SDL_SYS_WaitProcess(SDL_Process *process, SDL_bool block, int *exitcode); +void SDL_SYS_DestroyProcess(SDL_Process *process); diff --git a/src/process/dummy/SDL_dummyprocess.c b/src/process/dummy/SDL_dummyprocess.c new file mode 100644 index 000000000..6136bb19b --- /dev/null +++ b/src/process/dummy/SDL_dummyprocess.c @@ -0,0 +1,48 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 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, SDL_bool force) +{ + return SDL_Unsupported(); +} + +bool SDL_SYS_WaitProcess(SDL_Process *process, SDL_bool block, int *exitcode) +{ + return SDL_Unsupported(); +} + +void SDL_SYS_DestroyProcess(SDL_Process *process) +{ + return; +} + +#endif // SDL_PROCESS_DUMMY diff --git a/src/process/posix/SDL_posixprocess.c b/src/process/posix/SDL_posixprocess.c new file mode 100644 index 000000000..69cc80236 --- /dev/null +++ b/src/process/posix/SDL_posixprocess.c @@ -0,0 +1,401 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 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 "../SDL_sysprocess.h" +#include "../../file/SDL_iostream_c.h" + +#if defined(SDL_PLATFORM_MACOS) +#include +#define environ (*_NSGetEnviron()) +#elif defined(SDL_PLATFORM_FREEBSD) +#include +#define environ ((char **)dlsym(RTLD_DEFAULT, "environ")) +#else +extern char **environ; +#endif + +#define READ_END 0 +#define WRITE_END 1 + +struct SDL_ProcessData { + pid_t pid; +}; + +static void CleanupStream(void *userdata, void *value) +{ + SDL_Process *process = (SDL_Process *)value; + const char *property = (const char *)userdata; + + SDL_ClearProperty(process->props, property); +} + +static bool SetupStream(SDL_Process *process, int fd, const char *mode, const char *property) +{ + // 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 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); + + 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; +} + +bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID props) +{ + char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL); + char * const *env = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, environ); + 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 + SDL_ProcessData *data = SDL_calloc(1, sizeof(*data)); + if (!data) { + 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; + } + + 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 (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); + + 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); + + 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]); + } + 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 = waitpid(process->internal->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/src/process/windows/SDL_windowsprocess.c b/src/process/windows/SDL_windowsprocess.c new file mode 100644 index 000000000..7c9651428 --- /dev/null +++ b/src/process/windows/SDL_windowsprocess.c @@ -0,0 +1,452 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 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 "../../file/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; + } + return true; +} + +static bool join_arguments(const char * const *args, char **args_out) +{ + size_t len; + int i; + int i_out; + char *result; + + 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; + 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 '"': + 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 = result; + return true; +} + +static bool join_env(const char * const *env, char **environment_out) +{ + size_t len; + const char * const *var; + char *result; + + if (!env) { + *environment_out = NULL; + return true; + } + + 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'; + + *environment_out = result; + 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); + const char * const *env = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, 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); + char *createprocess_cmdline = NULL; + char *createprocess_env = NULL; + STARTUPINFOA startup_info; + DWORD creation_flags; + char *create_process_cwd; + 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 }; + bool result = false; + + // Keep the malloc() before exec() so that an OOM won't run a process at all + SDL_ProcessData *data = SDL_calloc(1, sizeof(*data)); + if (!data) { + return false; + } + process->internal = data; + + if (!join_arguments(args, &createprocess_cmdline)) { + goto done; + } + + if (!join_env(env, &createprocess_env)) { + goto done; + } + + creation_flags = 0; + + 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; + + 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 (!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 (!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 (!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; + } + } + + // FIXME: This should use CreateProcessW() + // FIXME: current directory as extended option? SDL_CreatProcessWithProperties + create_process_cwd = NULL; + if (!CreateProcessA(NULL, createprocess_cmdline, NULL, NULL, TRUE, creation_flags, createprocess_env, create_process_cwd, &startup_info, &data->process_information)) { + WIN_SetError("CreateProcessA"); + 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); + + 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, SDL_bool force) +{ + if (!TerminateProcess(process->internal->process_information.hProcess, 1)) { + return WIN_SetError("TerminateProcess failed"); + } + return true; +} + +bool SDL_SYS_WaitProcess(SDL_Process *process, SDL_bool block, int *exitcode) +{ + DWORD result; + + result = WaitForSingleObject(process->internal->process_information.hProcess, block ? INFINITE : 0); + + if (result == WAIT_OBJECT_0 || result == WAIT_TIMEOUT) { + 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); + } + CloseHandle(data->process_information.hThread); + CloseHandle(data->process_information.hProcess); + SDL_free(data); +} + +#endif // SDL_PROCESS_WINDOWS diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 59735410d..034a297e1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -363,6 +363,9 @@ add_sdl_test_executable(testmouse SOURCES testmouse.c) add_sdl_test_executable(testoverlay NEEDS_RESOURCES TESTUTILS SOURCES testoverlay.c) add_sdl_test_executable(testplatform NONINTERACTIVE SOURCES testplatform.c) add_sdl_test_executable(testpower NONINTERACTIVE SOURCES testpower.c) +add_sdl_test_executable(testprocess NONINTERACTIVE THREADS NONINTERACTIVE_ARGS $ SOURCES testprocess.c) +add_sdl_test_executable(childprocess SOURCES childprocess.c) +add_dependencies(testprocess childprocess) add_sdl_test_executable(testfilesystem NONINTERACTIVE SOURCES testfilesystem.c) if(WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 4) add_sdl_test_executable(pretest SOURCES pretest.c NONINTERACTIVE NONINTERACTIVE_TIMEOUT 60) diff --git a/test/childprocess.c b/test/childprocess.c new file mode 100644 index 000000000..d76534ee7 --- /dev/null +++ b/test/childprocess.c @@ -0,0 +1,149 @@ +#include +#include +#include + +#include +#include + +#if defined(SDL_PLATFORM_WINDOWS) +#include +#elif defined(SDL_PLATFORM_MACOS) +#include +#define environ (*_NSGetEnviron()) +#else +extern char **environ; +#endif + +int main(int argc, char *argv[]) { + SDLTest_CommonState *state; + int i; + const char *expect_environment = NULL; + SDL_bool expect_environment_match = SDL_FALSE; + SDL_bool print_arguments = SDL_FALSE; + SDL_bool print_environment = SDL_FALSE; + SDL_bool stdin_to_stdout = SDL_FALSE; + SDL_bool stdin_to_stderr = SDL_FALSE; + int exit_code = 0; + + state = SDLTest_CommonCreateState(argv, 0); + + for (i = 1; i < argc;) { + int consumed = SDLTest_CommonArg(state, i); + if (SDL_strcmp(argv[i], "--print-arguments") == 0) { + print_arguments = SDL_TRUE; + consumed = 1; + } else if (SDL_strcmp(argv[i], "--print-environment") == 0) { + print_environment = SDL_TRUE; + consumed = 1; + } else if (SDL_strcmp(argv[i], "--expect-env") == 0) { + if (i + 1 < argc) { + expect_environment = argv[i + 1]; + consumed = 2; + } + } else if (SDL_strcmp(argv[i], "--stdin-to-stdout") == 0) { + stdin_to_stdout = SDL_TRUE; + consumed = 1; + } else if (SDL_strcmp(argv[i], "--stdin-to-stderr") == 0) { + stdin_to_stderr = SDL_TRUE; + consumed = 1; + } else if (SDL_strcmp(argv[i], "--stdout") == 0) { + if (i + 1 < argc) { + fprintf(stdout, "%s", argv[i + 1]); + consumed = 2; + } + } else if (SDL_strcmp(argv[i], "--stderr") == 0) { + if (i + 1 < argc) { + fprintf(stderr, "%s", argv[i + 1]); + consumed = 2; + } + } else if (SDL_strcmp(argv[i], "--exit-code") == 0) { + if (i + 1 < argc) { + char *endptr = NULL; + exit_code = SDL_strtol(argv[i + 1], &endptr, 0); + if (endptr && *endptr == '\0') { + consumed = 2; + } + } + } else if (SDL_strcmp(argv[i], "--") == 0) { + i++; + break; + } + if (consumed <= 0) { + const char *args[] = { + "[--print-arguments]", + "[--print-environment]", + "[--expect-env KEY=VAL]", + "[--stdin-to-stdout]", + "[--stdout TEXT]", + "[--stdin-to-stderr]", + "[--stderr TEXT]", + "[--exit-code EXIT_CODE]", + "[--] [ARG [ARG ...]]", + NULL + }; + SDLTest_CommonLogUsage(state, argv[0], args); + return 1; + } + i += consumed; + } + + if (print_arguments) { + int print_i; + for (print_i = 0; i + print_i < argc; print_i++) { + fprintf(stdout, "|%d=%s|\r\n", print_i, argv[i + print_i]); + } + } + + if (print_environment || expect_environment) { + +#if defined(SDL_PLATFORM_WINDOWS) + char *original_env = GetEnvironmentStrings(); + const char *env = original_env; + for (; env[0]; env += SDL_strlen(env) + 1) { +#else + char **envp = environ; + for (; *envp; envp++) { + const char *env = *envp; +#endif + if (print_environment) { + fprintf(stdout, "%s\n", env); + } + if (expect_environment) { + expect_environment_match |= SDL_strcmp(env, expect_environment) == 0; + } + } +#ifdef SDL_PLATFORM_WINDOWS + FreeEnvironmentStringsA(original_env); +#endif + } + + if (stdin_to_stdout || stdin_to_stderr) { + + for (;;) { + int c; + c = fgetc(stdin); + if (c == EOF) { + if (errno == EAGAIN) { + clearerr(stdin); + SDL_Delay(10); + continue; + } + break; + } + if (stdin_to_stdout) { + fputc(c, stdout); + fflush(stdout); + } + if (stdin_to_stderr) { + fputc(c, stderr); + } + } + } + + SDLTest_CommonDestroyState(state); + + if (expect_environment && !expect_environment_match) { + exit_code |= 0x1; + } + return exit_code; +} diff --git a/test/testprocess.c b/test/testprocess.c new file mode 100644 index 000000000..3d2ed5e19 --- /dev/null +++ b/test/testprocess.c @@ -0,0 +1,637 @@ +#include +#include +#include + +#ifdef SDL_PLATFORM_WINDOWS +#define EXE ".exe" +#else +#define EXE "" +#endif + +/* + * FIXME: Additional tests: + * - stdin to stdout + * - stdin to stderr + * - read env, using env set by parent process + * - exit codes + * - kill process + * - waiting twice on process + * - executing a non-existing program + * - executing a process linking to a shared library not in the search paths + * - piping processes + * - forwarding SDL_IOFromFile stream to process + * - forwarding process to SDL_IOFromFile stream + */ + +typedef struct { + const char *childprocess_path; +} TestProcessData; + +static TestProcessData parsed_args; + +static void SDLCALL setUpProcess(void **arg) { + *arg = &parsed_args; +} + +static const char *options[] = { "/path/to/childprocess" EXE, NULL }; + +static char *env_key_val_string(const char *key) { + const char *env = SDL_getenv(key); + size_t size_result; + char *result; + + if (env == NULL) { + return NULL; + } + size_result = SDL_strlen(key) + SDL_strlen(env) + 2; + result = SDL_malloc(size_result); + SDL_snprintf(result, size_result, "%s=%s", key, env); + return result; +} + +static char **DuplicateEnvironment(const char *key0, ...) +{ + va_list ap; + size_t count = 1; + size_t i; + const char *keyN; + char **result; + + if (key0) { + if (SDL_strchr(key0, '=') || SDL_getenv(key0)) { + count += 1; + } + + va_start(ap, key0); + for (;;) { + keyN = va_arg(ap, const char *); + if (keyN) { + if (SDL_strchr(keyN, '=') || SDL_getenv(keyN)) { + count += 1; + } + } else { + break; + } + } + va_end(ap); + } + + result = SDL_calloc(count, sizeof(char *)); + + i = 0; + if (key0) { + if (SDL_strchr(key0, '=')) { + result[i++] = SDL_strdup(key0); + } else if (SDL_getenv(key0)) { + result[i++] = env_key_val_string(key0); + } + va_start(ap, key0); + for (;;) { + keyN = va_arg(ap, const char *); + if (keyN) { + if (SDL_strchr(keyN, '=')) { + result[i++] = SDL_strdup(keyN); + } else if (SDL_getenv(keyN)) { + result[i++] = env_key_val_string(keyN); + } + } else { + break; + } + } + va_end(ap); + } + return result; +} + +static void DestroyEnvironment(char **environment) { + char **envp; + + if (!environment) { + return; + } + for (envp = environment; *envp; envp++) { + SDL_free(*envp); + } + SDL_free(environment); +} + +static int SDLCALL process_testArguments(void *arg) +{ + TestProcessData *data = (TestProcessData *)arg; + const char *process_args[] = { + data->childprocess_path, + "--print-arguments", + "--", + "", + " ", + "a b c", + "a\tb\tc\t", + "\"a b\" c", + "'a' 'b' 'c'", + "%d%%%s", + "\\t\\c", + NULL + }; + SDL_Process *process = NULL; + char *buffer; + int exit_code; + int i; + + process = SDL_CreateProcess(process_args, SDL_TRUE); + SDLTest_AssertCheck(process != NULL, "SDL_CreateProcess()"); + if (!process) { + goto failed; + } + + exit_code = 0xdeadbeef; + buffer = (char *)SDL_ReadProcess(process, NULL, &exit_code); + SDLTest_AssertCheck(buffer != NULL, "SDL_ReadProcess()"); + SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code); + if (!buffer) { + goto failed; + } + + for (i = 3; process_args[i]; i++) { + char line[64]; + SDL_snprintf(line, sizeof(line), "|%d=%s|", i - 3, process_args[i]); + SDLTest_AssertCheck(!!SDL_strstr(buffer, line), "Check %s is in output", line); + } + SDL_free(buffer); + + SDLTest_AssertPass("About to destroy process"); + SDL_DestroyProcess(process); + return TEST_COMPLETED; +failed: + SDL_DestroyProcess(process); + return TEST_ABORTED; +} + +static int SDLCALL process_testInheritedEnv(void *arg) +{ + TestProcessData *data = (TestProcessData *)arg; + const char *process_args[] = { + data->childprocess_path, + "--print-environment", + "--expect-env", NULL, + NULL, + }; + SDL_PropertiesID props; + SDL_Process *process = NULL; + Sint64 pid; + SDL_IOStream *process_stdout = NULL; + char buffer[256]; + SDL_bool wait_result; + int exit_code; + static const char *const TEST_ENV_KEY = "testprocess_environment"; + char *test_env_val = NULL; + + test_env_val = SDLTest_RandomAsciiStringOfSize(32); + SDLTest_AssertPass("Setting parent environment variable %s=%s", TEST_ENV_KEY, test_env_val); + SDL_setenv(TEST_ENV_KEY, test_env_val, 1); + SDL_snprintf(buffer, sizeof(buffer), "%s=%s", TEST_ENV_KEY, test_env_val); + process_args[3] = buffer; + + props = SDL_CreateProperties(); + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (void *)process_args); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP); + process = SDL_CreateProcessWithProperties(props); + SDL_DestroyProperties(props); + SDLTest_AssertCheck(process != NULL, "SDL_CreateProcessWithProperties()"); + if (!process) { + goto failed; + } + + props = SDL_GetProcessProperties(process); + SDLTest_AssertCheck(props != 0, "SDL_GetProcessProperties()"); + + pid = SDL_GetNumberProperty(props, SDL_PROP_PROCESS_PID_NUMBER, 0); + SDLTest_AssertCheck(pid != 0, "Checking process ID, expected non-zero, got %" SDL_PRIs64, pid); + + process_stdout = (SDL_IOStream *)SDL_GetPointerProperty(props, SDL_PROP_PROCESS_STDOUT_POINTER, NULL); + SDLTest_AssertCheck(process_stdout != NULL, "SDL_GetPointerProperty(SDL_PROP_PROCESS_STDOUT_POINTER) returns a valid IO stream"); + if (!process_stdout) { + goto failed; + } + + for (;;) { + size_t amount_read; + + amount_read = SDL_ReadIO(process_stdout, buffer, sizeof(buffer) - 1); + if (amount_read > 0) { + buffer[amount_read] = '\0'; + SDLTest_Log("READ: %s", buffer); + } else if (SDL_GetIOStatus(process_stdout) != SDL_IO_STATUS_NOT_READY) { + break; + } + SDL_Delay(10); + } + + SDLTest_AssertPass("About to wait on process"); + exit_code = 0xdeadbeef; + wait_result = SDL_WaitProcess(process, SDL_TRUE, &exit_code); + SDLTest_AssertCheck(wait_result == SDL_TRUE, "Process should have closed when closing stdin"); + SDLTest_AssertPass("exit_code will be != 0 when environment variable was not set"); + SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code); + SDLTest_AssertPass("About to destroy process"); + SDL_DestroyProcess(process); + SDL_free(test_env_val); + return TEST_COMPLETED; +failed: + SDL_free(test_env_val); + SDL_DestroyProcess(process); + return TEST_ABORTED; +} + +static int SDLCALL process_testNewEnv(void *arg) +{ + TestProcessData *data = (TestProcessData *)arg; + const char *process_args[] = { + data->childprocess_path, + "--print-environment", + "--expect-env", NULL, + NULL, + }; + char **process_env; + SDL_PropertiesID props; + SDL_Process *process = NULL; + Sint64 pid; + SDL_IOStream *process_stdout = NULL; + char buffer[256]; + SDL_bool wait_result; + int exit_code; + static const char *const TEST_ENV_KEY = "testprocess_environment"; + char *test_env_val = NULL; + + test_env_val = SDLTest_RandomAsciiStringOfSize(32); + SDL_snprintf(buffer, sizeof(buffer), "%s=%s", TEST_ENV_KEY, test_env_val); + process_args[3] = buffer; + + process_env = DuplicateEnvironment("PATH", "LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH", buffer, NULL); + + props = SDL_CreateProperties(); + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (void *)process_args); + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, (void *)process_env); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP); + process = SDL_CreateProcessWithProperties(props); + SDL_DestroyProperties(props); + SDLTest_AssertCheck(process != NULL, "SDL_CreateProcessWithProperties()"); + if (!process) { + goto failed; + } + + props = SDL_GetProcessProperties(process); + SDLTest_AssertCheck(props != 0, "SDL_GetProcessProperties()"); + + pid = SDL_GetNumberProperty(props, SDL_PROP_PROCESS_PID_NUMBER, 0); + SDLTest_AssertCheck(pid != 0, "Checking process ID, expected non-zero, got %" SDL_PRIs64, pid); + + process_stdout = (SDL_IOStream *)SDL_GetPointerProperty(props, SDL_PROP_PROCESS_STDOUT_POINTER, NULL); + SDLTest_AssertCheck(process_stdout != NULL, "SDL_GetPointerProperty(SDL_PROP_PROCESS_STDOUT_POINTER) returns a valid IO stream"); + if (!process_stdout) { + goto failed; + } + + for (;;) { + size_t amount_read; + + amount_read = SDL_ReadIO(process_stdout, buffer, sizeof(buffer) - 1); + if (amount_read > 0) { + buffer[amount_read] = '\0'; + SDLTest_Log("READ: %s", buffer); + } else if (SDL_GetIOStatus(process_stdout) != SDL_IO_STATUS_NOT_READY) { + break; + } + SDL_Delay(10); + } + + SDLTest_AssertPass("About to wait on process"); + exit_code = 0xdeadbeef; + wait_result = SDL_WaitProcess(process, SDL_TRUE, &exit_code); + SDLTest_AssertCheck(wait_result == SDL_TRUE, "Process should have closed when closing stdin"); + SDLTest_AssertPass("exit_code will be != 0 when environment variable was not set"); + SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code); + SDLTest_AssertPass("About to destroy process"); + SDL_DestroyProcess(process); + DestroyEnvironment(process_env); + SDL_free(test_env_val); + return TEST_COMPLETED; +failed: + SDL_free(test_env_val); + SDL_DestroyProcess(process); + DestroyEnvironment(process_env); + return TEST_ABORTED; +} + +static int process_testStdinToStdout(void *arg) +{ + TestProcessData *data = (TestProcessData *)arg; + const char *process_args[] = { + data->childprocess_path, + "--stdin-to-stdout", + NULL, + }; + const char **process_env = NULL; + SDL_PropertiesID props; + SDL_Process *process = NULL; + Sint64 pid; + SDL_IOStream *process_stdin = NULL; + SDL_IOStream *process_stdout = NULL; + const char *text_in = "Tests whether we can write to stdin and read from stdout\r\n{'succes': true, 'message': 'Success!'}\r\nYippie ka yee\r\nEOF"; + size_t amount_written; + size_t amount_to_write; + char buffer[128]; + size_t total_read; + SDL_bool wait_result; + int exit_code; + + props = SDL_CreateProperties(); + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (void *)process_args); + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, (void *)process_env); + 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); + SDLTest_AssertCheck(process != NULL, "SDL_CreateProcessWithProperties()"); + if (!process) { + goto failed; + } + + props = SDL_GetProcessProperties(process); + SDLTest_AssertCheck(props != 0, "SDL_GetProcessProperties()"); + + pid = SDL_GetNumberProperty(props, SDL_PROP_PROCESS_PID_NUMBER, 0); + SDLTest_AssertCheck(pid != 0, "Checking process ID, expected non-zero, got %" SDL_PRIs64, pid); + + process_stdin = (SDL_IOStream *)SDL_GetPointerProperty(props, SDL_PROP_PROCESS_STDIN_POINTER, NULL); + SDLTest_AssertCheck(process_stdin != NULL, "SDL_GetPointerProperty(SDL_PROP_PROCESS_STDIN_POINTER) returns a valid IO stream"); + process_stdout = (SDL_IOStream *)SDL_GetPointerProperty(props, SDL_PROP_PROCESS_STDOUT_POINTER, NULL); + SDLTest_AssertCheck(process_stdout != NULL, "SDL_GetPointerProperty(SDL_PROP_PROCESS_STDOUT_POINTER) returns a valid IO stream"); + if (!process_stdin || !process_stdout) { + goto failed; + } + SDLTest_AssertPass("About to write to process"); + amount_to_write = SDL_strlen(text_in); + amount_written = SDL_WriteIO(process_stdin, text_in, amount_to_write); + SDLTest_AssertCheck(amount_written == amount_to_write, "SDL_WriteIO(subprocess.stdin) wrote %" SDL_PRIu64 " bytes, expected %" SDL_PRIu64, (Uint64)amount_written, (Uint64)amount_to_write); + if (amount_to_write != amount_written) { + goto failed; + } + SDL_FlushIO(process_stdin); + + total_read = 0; + buffer[0] = '\0'; + for (;;) { + size_t amount_read; + if (total_read >= sizeof(buffer) - 1) { + SDLTest_AssertCheck(0, "Buffer is too small for input data."); + goto failed; + } + + SDLTest_AssertPass("About to read from process"); + amount_read = SDL_ReadIO(process_stdout, buffer + total_read, sizeof(buffer) - total_read - 1); + if (amount_read == 0 && SDL_GetIOStatus(process_stdout) != SDL_IO_STATUS_NOT_READY) { + break; + } + total_read += amount_read; + buffer[total_read] = '\0'; + if (total_read >= sizeof(buffer) - 1 || SDL_strstr(buffer, "EOF")) { + break; + } + SDL_Delay(10); + } + SDLTest_Log("Text read from subprocess: %s", buffer); + SDLTest_AssertCheck(SDL_strcmp(buffer, text_in) == 0, "Subprocess stdout should match text written to stdin"); + + SDLTest_AssertPass("About to close stdin"); + /* Closing stdin of `subprocessstdin --stdin-to-stdout` should close the process */ + SDL_CloseIO(process_stdin); + + process_stdin = (SDL_IOStream *)SDL_GetPointerProperty(props, SDL_PROP_PROCESS_STDIN_POINTER, NULL); + SDLTest_AssertCheck(process_stdin == NULL, "SDL_GetPointerProperty(SDL_PROP_PROCESS_STDIN_POINTER) is cleared after close"); + + SDLTest_AssertPass("About to wait on process"); + exit_code = 0xdeadbeef; + wait_result = SDL_WaitProcess(process, SDL_TRUE, &exit_code); + SDLTest_AssertCheck(wait_result == SDL_TRUE, "Process should have closed when closing stdin"); + SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code); + if (!wait_result) { + SDL_bool killed; + SDL_Log("About to kill process"); + killed = SDL_KillProcess(process, SDL_TRUE); + SDLTest_AssertCheck(killed, "SDL_KillProcess succeeded"); + } + SDLTest_AssertPass("About to destroy process"); + SDL_DestroyProcess(process); + return TEST_COMPLETED; +failed: + + SDL_DestroyProcess(process); + return TEST_ABORTED; +} + +static int process_testSimpleStdinToStdout(void *arg) +{ + TestProcessData *data = (TestProcessData *)arg; + const char *process_args[] = { + data->childprocess_path, + "--stdin-to-stdout", + NULL, + }; + SDL_Process *process = NULL; + const char *text_in = "Tests whether we can write to stdin and read from stdout\r\n{'succes': true, 'message': 'Success!'}\r\nYippie ka yee\r\nEOF"; + char *buffer; + SDL_bool result; + int exit_code; + + process = SDL_CreateProcess(process_args, SDL_TRUE); + SDLTest_AssertCheck(process != NULL, "SDL_CreateProcess()"); + if (!process) { + goto failed; + } + + SDLTest_AssertPass("About to write to process"); + result = SDL_WriteProcess(process, text_in, SDL_strlen(text_in), SDL_TRUE); + SDLTest_AssertCheck(result, "SDL_WriteProcess()"); + if (!result) { + goto failed; + } + + exit_code = 0xdeadbeef; + buffer = (char *)SDL_ReadProcess(process, NULL, &exit_code); + SDLTest_AssertCheck(buffer != NULL, "SDL_ReadProcess()"); + SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code); + if (!buffer) { + goto failed; + } + + SDLTest_Log("Text read from subprocess: %s", buffer); + SDLTest_AssertCheck(SDL_strcmp(buffer, text_in) == 0, "Subprocess stdout should match text written to stdin"); + SDL_free(buffer); + + SDLTest_AssertPass("About to destroy process"); + SDL_DestroyProcess(process); + return TEST_COMPLETED; + +failed: + SDL_DestroyProcess(process); + return TEST_ABORTED; +} + +static int process_testMultiprocessStdinToStdout(void *arg) +{ + TestProcessData *data = (TestProcessData *)arg; + const char *process_args[] = { + data->childprocess_path, + "--stdin-to-stdout", + NULL, + }; + SDL_Process *process1 = NULL; + SDL_Process *process2 = NULL; + SDL_PropertiesID props; + const char *text_in = "Tests whether we can write to stdin and read from stdout\r\n{'succes': true, 'message': 'Success!'}\r\nYippie ka yee\r\nEOF"; + char *buffer; + SDL_bool result; + int exit_code; + + process1 = SDL_CreateProcess(process_args, SDL_TRUE); + SDLTest_AssertCheck(process1 != NULL, "SDL_CreateProcess()"); + if (!process1) { + goto failed; + } + + props = SDL_CreateProperties(); + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (void *)process_args); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_REDIRECT); + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_POINTER, SDL_GetPointerProperty(SDL_GetProcessProperties(process1), SDL_PROP_PROCESS_STDOUT_POINTER, NULL)); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP); + process2 = SDL_CreateProcessWithProperties(props); + SDL_DestroyProperties(props); + SDLTest_AssertCheck(process2 != NULL, "SDL_CreateProcess()"); + if (!process2) { + goto failed; + } + + SDLTest_AssertPass("About to write to process"); + result = SDL_WriteProcess(process1, text_in, SDL_strlen(text_in), SDL_TRUE); + SDLTest_AssertCheck(result, "SDL_WriteProcess()"); + if (!result) { + goto failed; + } + + exit_code = 0xdeadbeef; + buffer = (char *)SDL_ReadProcess(process2, NULL, &exit_code); + SDLTest_AssertCheck(buffer != NULL, "SDL_ReadProcess()"); + SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code); + if (!buffer) { + goto failed; + } + + SDLTest_Log("Text read from subprocess: %s", buffer); + SDLTest_AssertCheck(SDL_strcmp(buffer, text_in) == 0, "Subprocess stdout should match text written to stdin"); + SDL_free(buffer); + SDLTest_AssertPass("About to destroy processes"); + SDL_DestroyProcess(process1); + SDL_DestroyProcess(process2); + return TEST_COMPLETED; + +failed: + SDL_DestroyProcess(process1); + SDL_DestroyProcess(process2); + return TEST_ABORTED; +} + +static const SDLTest_TestCaseReference processTestArguments = { + process_testArguments, "process_testArguments", "Test passing arguments to child process", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference processTestIneritedEnv = { + process_testInheritedEnv, "process_testInheritedEnv", "Test inheriting environment from parent process", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference processTestNewEnv = { + process_testNewEnv, "process_testNewEnv", "Test creating new environment for child process", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference processTestStdinToStdout = { + process_testStdinToStdout, "process_testStdinToStdout", "Test writing to stdin and reading from stdout", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference processTestSimpleStdinToStdout = { + process_testSimpleStdinToStdout, "process_testSimpleStdinToStdout", "Test writing to stdin and reading from stdout using the simplified API", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference processTestMultiprocessStdinToStdout = { + process_testMultiprocessStdinToStdout, "process_testMultiprocessStdinToStdout", "Test writing to stdin and reading from stdout using the simplified API", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference *processTests[] = { + &processTestArguments, + &processTestIneritedEnv, + &processTestNewEnv, + &processTestStdinToStdout, + &processTestSimpleStdinToStdout, + &processTestMultiprocessStdinToStdout, + NULL +}; + +static SDLTest_TestSuiteReference processTestSuite = { + "Process", + setUpProcess, + processTests, + NULL +}; + +static SDLTest_TestSuiteReference *testSuites[] = { + &processTestSuite, + NULL +}; + +int main(int argc, char *argv[]) +{ + int i; + int result; + SDLTest_CommonState *state; + SDLTest_TestSuiteRunner *runner; + + /* Initialize test framework */ + state = SDLTest_CommonCreateState(argv, 0); + if (!state) { + return 1; + } + + runner = SDLTest_CreateTestSuiteRunner(state, testSuites); + + /* Enable standard application logging */ + SDL_SetLogPriority(SDL_LOG_CATEGORY_TEST, SDL_LOG_PRIORITY_INFO); + + /* Parse commandline */ + for (i = 1; i < argc;) { + int consumed; + + consumed = SDLTest_CommonArg(state, i); + if (!consumed) { + if (!parsed_args.childprocess_path) { + parsed_args.childprocess_path = argv[i]; + consumed = 1; + } + } + if (consumed <= 0) { + SDLTest_CommonLogUsage(state, argv[0], options); + return 1; + } + + i += consumed; + } + + if (!parsed_args.childprocess_path) { + SDLTest_CommonLogUsage(state, argv[0], options); + return 1; + } + + result = SDLTest_ExecuteTestSuiteRunner(runner); + + SDL_Quit(); + SDLTest_DestroyTestSuiteRunner(runner); + SDLTest_CommonDestroyState(state); + return result; +}