diff --git a/CMakeLists.txt b/CMakeLists.txt index 585119e84a82a..df8f5710f4d83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4000,6 +4000,9 @@ if(SDL_SHARED) if(NOT CMAKE_VERSION VERSION_LESS "3.16") target_precompile_headers(SDL3-shared PRIVATE "$<$,$>:${PROJECT_SOURCE_DIR}/src/SDL_internal.h>") endif() + if(HAIKU) + target_link_libraries(SDL3-shared PUBLIC network) + endif() endif() if(SDL_STATIC) @@ -4024,6 +4027,9 @@ if(SDL_STATIC) if(NOT CMAKE_VERSION VERSION_LESS "3.16") target_precompile_headers(SDL3-static PRIVATE "$<$,$>:${PROJECT_SOURCE_DIR}/src/SDL_internal.h>") endif() + if(HAIKU) + target_link_libraries(SDL3-static PUBLIC network) + endif() endif() sdl_compile_definitions( @@ -4055,6 +4061,9 @@ if(SDL_TEST_LIBRARY) target_link_libraries(SDL3_test PRIVATE ${EXTRA_TEST_LIBS}) set_property(TARGET SDL3_test APPEND PROPERTY COMPATIBLE_INTERFACE_STRING "SDL_VERSION") set_property(TARGET SDL3_test PROPERTY INTERFACE_SDL_VERSION "SDL${SDL3_VERSION_MAJOR}") + if(HAIKU) + target_link_libraries(SDL3_test PUBLIC network) + endif() endif() ##### Configure installation folders ##### diff --git a/include/SDL3/SDL_process.h b/include/SDL3/SDL_process.h index 57e3afd94c233..9344e2475f25c 100644 --- a/include/SDL3/SDL_process.h +++ b/include/SDL3/SDL_process.h @@ -63,6 +63,11 @@ extern "C" { */ typedef struct SDL_Process SDL_Process; +/** + * An opaque handle representing an IPC channel. + */ +typedef struct SDL_IPC SDL_IPC; + /** * Create a new process. * @@ -201,6 +206,8 @@ typedef enum SDL_ProcessIO * property is only important if you want to start programs that does * non-standard command-line processing, and in most cases using * `SDL_PROP_PROCESS_CREATE_ARGS_POINTER` is sufficient. + * - `SDL_PROP_PROCESS_CREATE_SDL_IPC`: true if the process should create an + * IPC channel for sharing resources. * * On POSIX platforms, wait() and waitpid(-1, ...) should not be called, and * SIGCHLD should not be ignored or handled because those would prevent SDL @@ -238,6 +245,7 @@ extern SDL_DECLSPEC SDL_Process * SDLCALL SDL_CreateProcessWithProperties(SDL_Pr #define SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN "SDL.process.create.stderr_to_stdout" #define SDL_PROP_PROCESS_CREATE_BACKGROUND_BOOLEAN "SDL.process.create.background" #define SDL_PROP_PROCESS_CREATE_CMDLINE_STRING "SDL.process.create.cmdline" +#define SDL_PROP_PROCESS_CREATE_SDL_IPC "SDL.process.create.ipc" /** * Get the properties associated with a process. @@ -256,6 +264,8 @@ extern SDL_DECLSPEC SDL_Process * SDLCALL SDL_CreateProcessWithProperties(SDL_Pr * `SDL_PROP_PROCESS_CREATE_STDERR_NUMBER` set to `SDL_PROCESS_STDIO_APP`. * - `SDL_PROP_PROCESS_BACKGROUND_BOOLEAN`: true if the process is running in * the background. + * - `SDL_PROP_PROCESS_SDL_IPC`: true if the process has an IPC channel for + * SDL shared resources. * * \param process the process to query. * \returns a valid property ID on success or 0 on failure; call @@ -275,6 +285,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetProcessProperties(SDL_Proces #define SDL_PROP_PROCESS_STDOUT_POINTER "SDL.process.stdout" #define SDL_PROP_PROCESS_STDERR_POINTER "SDL.process.stderr" #define SDL_PROP_PROCESS_BACKGROUND_BOOLEAN "SDL.process.background" +#define SDL_PROP_PROCESS_SDL_IPC "SDL.process.ipc" /** * Read all the output from a process. @@ -432,6 +443,25 @@ extern SDL_DECLSPEC bool SDLCALL SDL_WaitProcess(SDL_Process *process, bool bloc */ extern SDL_DECLSPEC void SDLCALL SDL_DestroyProcess(SDL_Process *process); +/** + * Returns a pointer to the process IPC, or NULL if the process has no active + * IPC. + * + * \param process The process to retrieve IPC from. + * \returns A handle to the IPC for the given process, or NULL if the process + * has no active IPC. + */ +extern SDL_DECLSPEC SDL_IPC * SDLCALL SDL_GetProcessIPC(SDL_Process *process); + +/** + * Returns a pointer to the SDL IPC for the parent process, or NULL if the + * parent process did not set up SDL IPC. + * + * \returns A handle to the IPC of an SDL-based parent, or NULL if no IPC + * was found. + */ +extern SDL_DECLSPEC SDL_IPC * SDLCALL SDL_GetParentIPC(void); + /* Ends C function definitions when using C++ */ #ifdef __cplusplus } diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index de64a3efad92a..8eb9f5a2f6fdf 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1270,6 +1270,8 @@ SDL3_0.0.0 { SDL_RotateSurface; SDL_LoadSurface_IO; SDL_LoadSurface; + SDL_GetProcessIPC; + SDL_GetParentIPC; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index cc53044efb606..0abc20b9a0798 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1296,3 +1296,5 @@ #define SDL_RotateSurface SDL_RotateSurface_REAL #define SDL_LoadSurface_IO SDL_LoadSurface_IO_REAL #define SDL_LoadSurface SDL_LoadSurface_REAL +#define SDL_GetProcessIPC SDL_GetProcessIPC_REAL +#define SDL_GetParentIPC SDL_GetParentIPC_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 9e3c9cdbef2bd..222993785f86d 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1304,3 +1304,5 @@ SDL_DYNAPI_PROC(SDL_Cursor*,SDL_CreateAnimatedCursor,(SDL_CursorFrameInfo *a,int SDL_DYNAPI_PROC(SDL_Surface*,SDL_RotateSurface,(SDL_Surface *a,float b),(a,b),return) SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadSurface_IO,(SDL_IOStream *a,bool b),(a,b),return) SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadSurface,(const char *a),(a),return) +SDL_DYNAPI_PROC(SDL_IPC*,SDL_GetProcessIPC,(SDL_Process *a),(a),return) +SDL_DYNAPI_PROC(SDL_IPC*,SDL_GetParentIPC,(void),(),return) diff --git a/src/process/SDL_process.c b/src/process/SDL_process.c index e7316bb28190c..98f381ffd2d99 100644 --- a/src/process/SDL_process.c +++ b/src/process/SDL_process.c @@ -202,3 +202,13 @@ void SDL_DestroyProcess(SDL_Process *process) SDL_DestroyProperties(process->props); SDL_free(process); } + +SDL_IPC * SDL_GetProcessIPC(SDL_Process *process) +{ + return SDL_SYS_GetProcessIPC(process); +} + +SDL_IPC * SDL_GetParentIPC(void) +{ + return SDL_SYS_GetParentIPC(); +} diff --git a/src/process/SDL_sysprocess.h b/src/process/SDL_sysprocess.h index 8d8932ef9ee57..efb7ef5247f79 100644 --- a/src/process/SDL_sysprocess.h +++ b/src/process/SDL_sysprocess.h @@ -38,5 +38,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID bool SDL_SYS_KillProcess(SDL_Process *process, bool force); bool SDL_SYS_WaitProcess(SDL_Process *process, bool block, int *exitcode); void SDL_SYS_DestroyProcess(SDL_Process *process); +SDL_IPC * SDL_SYS_GetProcessIPC(SDL_Process *process); +SDL_IPC * SDL_SYS_GetParentIPC(void); #endif // SDL_sysprocess_h_ diff --git a/src/process/dummy/SDL_dummyprocess.c b/src/process/dummy/SDL_dummyprocess.c index ba69e4a04a168..50b1d3aa5fd8f 100644 --- a/src/process/dummy/SDL_dummyprocess.c +++ b/src/process/dummy/SDL_dummyprocess.c @@ -24,7 +24,6 @@ #include "../SDL_sysprocess.h" - bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID props) { return SDL_Unsupported(); @@ -45,4 +44,14 @@ void SDL_SYS_DestroyProcess(SDL_Process *process) return; } +SDL_IPC * SDL_SYS_GetProcessIPC(SDL_Process *process) +{ + return NULL; +} + +SDL_IPC * SDL_SYS_GetParentIPC(void) +{ + return NULL; +} + #endif // SDL_PROCESS_DUMMY diff --git a/src/process/posix/SDL_posixprocess.c b/src/process/posix/SDL_posixprocess.c index 627f0de688538..9a4938decb916 100644 --- a/src/process/posix/SDL_posixprocess.c +++ b/src/process/posix/SDL_posixprocess.c @@ -22,6 +22,8 @@ #ifdef SDL_PROCESS_POSIX +#define SDLIPC_FILENO 3 + #include #include #include @@ -32,6 +34,8 @@ #include #include #include +#include +#include #include "../SDL_sysprocess.h" #include "../../io/SDL_iostream_c.h" @@ -46,8 +50,24 @@ #define READ_END 0 #define WRITE_END 1 +#define PARENT_END 0 +#define CHILD_END 1 + +#define SDL_IPC_ENVVAR "_SDL_IPC" + +#define _STR(VALUE) #VALUE + +// Allows you to stringify the expanded value of a +// macro +#define STR(MACRO) _STR(MACRO) + +struct SDL_IPC { + int socket; +}; + struct SDL_ProcessData { pid_t pid; + SDL_IPC ipc; }; static void CleanupStream(void *userdata, void *value) @@ -88,6 +108,19 @@ static void IgnoreSignal(int sig) } } +static bool CreateSockets(int fds[2]) +{ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, 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 CreatePipe(int fds[2]) { if (pipe(fds) < 0) { @@ -122,14 +155,15 @@ static bool GetStreamFD(SDL_PropertiesID props, const char *property, int *resul return true; } -static bool AddFileDescriptorCloseActions(posix_spawn_file_actions_t *fa) +static bool AddFileDescriptorCloseActions(posix_spawn_file_actions_t *fa, bool has_ipc) { DIR *dir = opendir("/proc/self/fd"); + const int keep_fileno = has_ipc ? SDLIPC_FILENO : STDERR_FILENO; if (dir) { struct dirent *entry; while ((entry = readdir(dir)) != NULL) { int fd = SDL_atoi(entry->d_name); - if (fd <= STDERR_FILENO) { + if (fd <= keep_fileno) { continue; } @@ -144,7 +178,7 @@ static bool AddFileDescriptorCloseActions(posix_spawn_file_actions_t *fa) } closedir(dir); } else { - for (int fd = (int)(sysconf(_SC_OPEN_MAX) - 1); fd > STDERR_FILENO; --fd) { + for (int fd = (int)(sysconf(_SC_OPEN_MAX) - 1); fd > keep_fileno; --fd) { int flags = fcntl(fd, F_GETFD); if (flags < 0 || (flags & FD_CLOEXEC)) { continue; @@ -168,11 +202,22 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID 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); + bool sdl_ipc = SDL_GetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_SDL_IPC, false); int stdin_pipe[2] = { -1, -1 }; int stdout_pipe[2] = { -1, -1 }; int stderr_pipe[2] = { -1, -1 }; + int sockets[2] = { -1, -1 }; int fd = -1; + if (sdl_ipc) { + // allows children who have already opened a new file descriptor + // to distinguish between "fd 3 came from SDL" vs. "fd 3 is the fd I just opened" + // + // also gives us the option to change fd logic in the future without breaking + // clients + SDL_SetEnvironmentVariable(env, SDL_IPC_ENVVAR, STR(SDLIPC_FILENO), true); + } + // Keep the malloc() before exec() so that an OOM won't run a process at all envp = SDL_GetEnvironmentVariables(env); if (!envp) { @@ -296,6 +341,16 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID break; } + if (sdl_ipc) { + if (!CreateSockets(sockets)) { + goto posix_spawn_fail_all; + } + if (posix_spawn_file_actions_adddup2(&fa, sockets[CHILD_END], SDLIPC_FILENO) != 0) { + SDL_SetError("posix_spawn_file_actions_adddup2 failed: %s", strerror(errno)); + goto posix_spawn_fail_all; + } + } + 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)); @@ -333,7 +388,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID } } - if (!AddFileDescriptorCloseActions(&fa)) { + if (!AddFileDescriptorCloseActions(&fa, sdl_ipc)) { goto posix_spawn_fail_all; } @@ -400,6 +455,12 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID close(stderr_pipe[WRITE_END]); } + if (sdl_ipc) { + data->ipc.socket = sockets[PARENT_END]; + close(sockets[CHILD_END]); + } + SDL_SetBooleanProperty(process->props, SDL_PROP_PROCESS_SDL_IPC, sdl_ipc); + posix_spawn_file_actions_destroy(&fa); posix_spawnattr_destroy(&attr); SDL_free(envp); @@ -433,6 +494,12 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID if (stderr_pipe[WRITE_END] >= 0) { close(stderr_pipe[WRITE_END]); } + if (sockets[PARENT_END] >= 0) { + close(sockets[PARENT_END]); + } + if (sockets[CHILD_END] >= 0) { + close(sockets[CHILD_END]); + } SDL_free(envp); return false; } @@ -511,4 +578,55 @@ void SDL_SYS_DestroyProcess(SDL_Process *process) SDL_free(process->internal); } +SDL_IPC * SDL_SYS_GetProcessIPC(SDL_Process *process) +{ + SDL_ProcessData *data = (SDL_ProcessData*)process->internal; + return &data->ipc; +} + +SDL_IPC * SDL_SYS_GetParentIPC(void) +{ + const char *SDL_GetEnvironmentVariable(SDL_Environment *env, const char *name); + + // hmmmm + static SDL_IPC ipc = { + .socket = -1, + }; + + SDL_Environment *env = NULL; + + if (ipc.socket == -1) { + env = SDL_GetEnvironment(); + if (!env) { + goto return_null; + } + + const char *env_fd = SDL_GetEnvironmentVariable(env, SDL_IPC_ENVVAR); + if (!env_fd) { + goto destroy_environment; + } + + char *end = NULL; + long result = strtol(env_fd, &end, 10); + + const bool error = end == NULL + || end == env_fd + || result > INT_MAX + || result < 0; + + if (error) { + goto destroy_environment; + } + + ipc.socket = (int)result; + } + + return &ipc; + +destroy_environment: + SDL_DestroyEnvironment(env); +return_null: + return NULL; +} + #endif // SDL_PROCESS_POSIX diff --git a/src/process/windows/SDL_windowsprocess.c b/src/process/windows/SDL_windowsprocess.c index cae6cfe1fc82c..44c0d5b355a3b 100644 --- a/src/process/windows/SDL_windowsprocess.c +++ b/src/process/windows/SDL_windowsprocess.c @@ -602,4 +602,16 @@ void SDL_SYS_DestroyProcess(SDL_Process *process) SDL_free(data); } +SDL_IPC * SDL_SYS_GetProcessIPC(SDL_Process *process) +{ + // TODO: implement this + return NULL; +} + +SDL_IPC * SDL_SYS_GetParentIPC(void) +{ + // TODO: implement this + return NULL; +} + #endif // SDL_PROCESS_WINDOWS diff --git a/test/childprocess.c b/test/childprocess.c index 69a7cea2da3d9..fdd8dbb20059a 100644 --- a/test/childprocess.c +++ b/test/childprocess.c @@ -18,6 +18,7 @@ int main(int argc, char *argv[]) { bool stdin_to_stdout = false; bool read_stdin = false; bool stdin_to_stderr = false; + bool sdl_ipc = false; SDL_IOStream *log_stdin = NULL; int exit_code = 0; @@ -60,6 +61,9 @@ int main(int argc, char *argv[]) { } consumed = 2; } + } else if (SDL_strcmp(argv[i], "--sdl-ipc") == 0) { + sdl_ipc = true; + consumed = 1; } else if (SDL_strcmp(argv[i], "--exit-code") == 0) { if (i + 1 < argc) { char *endptr = NULL; @@ -95,6 +99,7 @@ int main(int argc, char *argv[]) { "[--stdout TEXT]", "[--stdin-to-stderr]", "[--stderr TEXT]", + "[--sdl-ipc]", "[--exit-code EXIT_CODE]", "[--] [ARG [ARG ...]]", NULL @@ -174,6 +179,15 @@ int main(int argc, char *argv[]) { SDL_CloseIO(log_stdin); } + if (sdl_ipc) { + SDL_IPC *ipc = SDL_GetParentIPC(); + if (!ipc) { + SDL_Log("SDL IPC was requested but was not opened"); + } else { + puts("SDL IPC opened successfully"); + } + } + SDLTest_CommonDestroyState(state); return exit_code; diff --git a/test/testprocess.c b/test/testprocess.c index af6ebcd94cc90..0e1bb2d7a8d0d 100644 --- a/test/testprocess.c +++ b/test/testprocess.c @@ -1127,6 +1127,63 @@ static int process_testWindowsCmdlinePrecedence(void *arg) return TEST_ABORTED; } +static int SDLCALL process_testIPC(void *arg) +{ + TestProcessData *data = (TestProcessData *)arg; + SDL_PropertiesID props; + SDL_Process *process = NULL; + const char *process_args[] = { + data->childprocess_path, + "--sdl-ipc", + NULL, + }; + char *buffer; + size_t total_read; + int exit_code; + +#ifndef SDL_PLATFORM_UNIX + SDLTest_AssertPass("SDL_IPC is currently only implemented for Posix"); + return TEST_SKIPPED; +#endif + + props = SDL_CreateProperties(); + SDLTest_AssertCheck(props != 0, "SDL_CreateProperties()"); + if (!props) { + goto failed; + } + + SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (void*)process_args); + SDL_SetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_SDL_IPC, true); + SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP); + + process = SDL_CreateProcessWithProperties(props); + SDLTest_AssertCheck(process != NULL, "SDL_CreateProcessWithProperties()"); + if (!process) { + goto failed; + } + + SDL_IPC *ipc = SDL_GetProcessIPC(process); + SDLTest_AssertCheck(ipc != NULL, "SDL_GetProcessIPC()"); + + buffer = SDL_ReadProcess(process, &total_read, &exit_code); + SDLTest_AssertCheck(buffer != NULL, "SDL_ReadProcess()"); + if (!buffer) { + goto failed; + } + SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code); + SDLTest_AssertCheck(!!SDL_strstr(buffer, "SDL IPC opened successfully"), "Check \"SDL IPC opened successfully\" is printed"); + + SDL_DestroyProperties(props); + SDL_DestroyProcess(process); + SDL_free(buffer); + return TEST_COMPLETED; + +failed: + SDL_DestroyProperties(props); + SDL_DestroyProcess(process); + return TEST_ABORTED; +} + static const SDLTest_TestCaseReference processTestArguments = { process_testArguments, "process_testArguments", "Test passing arguments to child process", TEST_ENABLED }; @@ -1187,6 +1244,10 @@ static const SDLTest_TestCaseReference processTestWindowsCmdlinePrecedence = { process_testWindowsCmdlinePrecedence, "process_testWindowsCmdlinePrecedence", "Test SDL_PROP_PROCESS_CREATE_CMDLINE_STRING precedence over SDL_PROP_PROCESS_CREATE_ARGS_POINTER", TEST_ENABLED }; +static const SDLTest_TestCaseReference processTestIPC = { + process_testIPC, "process_testIPC", "Test creating IPC for child processes", TEST_ENABLED +}; + static const SDLTest_TestCaseReference *processTests[] = { &processTestArguments, &processTestExitCode, @@ -1203,6 +1264,7 @@ static const SDLTest_TestCaseReference *processTests[] = { &processTestFileRedirection, &processTestWindowsCmdline, &processTestWindowsCmdlinePrecedence, + &processTestIPC, NULL };