Atlas - SDL_posixprocess.c

Home / ext / SDL / src / process / posix Lines: 1 | Size: 17182 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#ifdef SDL_PROCESS_POSIX 24 25#include <dirent.h> 26#include <fcntl.h> 27#include <errno.h> 28#include <signal.h> 29#include <spawn.h> 30#include <stdio.h> 31#include <stdlib.h> 32#include <string.h> 33#include <unistd.h> 34#include <sys/wait.h> 35 36#include "../SDL_sysprocess.h" 37#include "../../io/SDL_iostream_c.h" 38 39 40#if defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP) && \ 41 !defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR) 42#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR 43#define posix_spawn_file_actions_addchdir posix_spawn_file_actions_addchdir_np 44#endif 45 46#define READ_END 0 47#define WRITE_END 1 48 49struct SDL_ProcessData { 50 pid_t pid; 51}; 52 53static void CleanupStream(void *userdata, void *value) 54{ 55 SDL_Process *process = (SDL_Process *)value; 56 const char *property = (const char *)userdata; 57 58 SDL_ClearProperty(process->props, property); 59} 60 61static bool SetupStream(SDL_Process *process, int fd, const char *mode, const char *property) 62{ 63 // Set the file descriptor to non-blocking mode 64 fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); 65 66 SDL_IOStream *io = SDL_IOFromFD(fd, true); 67 if (!io) { 68 return false; 69 } 70 71 SDL_SetPointerPropertyWithCleanup(SDL_GetIOProperties(io), "SDL.internal.process", process, CleanupStream, (void *)property); 72 SDL_SetPointerProperty(process->props, property, io); 73 return true; 74} 75 76static void IgnoreSignal(int sig) 77{ 78 struct sigaction action; 79 80 sigaction(SIGPIPE, NULL, &action); 81#ifdef HAVE_SA_SIGACTION 82 if (action.sa_handler == SIG_DFL && (void (*)(int))action.sa_sigaction == SIG_DFL) { 83#else 84 if (action.sa_handler == SIG_DFL) { 85#endif 86 action.sa_handler = SIG_IGN; 87 sigaction(sig, &action, NULL); 88 } 89} 90 91static bool CreatePipe(int fds[2]) 92{ 93 if (pipe(fds) < 0) { 94 return false; 95 } 96 97 // Make sure the pipe isn't accidentally inherited by another thread creating a process 98 fcntl(fds[READ_END], F_SETFD, fcntl(fds[READ_END], F_GETFD) | FD_CLOEXEC); 99 fcntl(fds[WRITE_END], F_SETFD, fcntl(fds[WRITE_END], F_GETFD) | FD_CLOEXEC); 100 101 // Make sure we don't crash if we write when the pipe is closed 102 IgnoreSignal(SIGPIPE); 103 104 return true; 105} 106 107static bool GetStreamFD(SDL_PropertiesID props, const char *property, int *result) 108{ 109 SDL_IOStream *io = (SDL_IOStream *)SDL_GetPointerProperty(props, property, NULL); 110 if (!io) { 111 SDL_SetError("%s is not set", property); 112 return false; 113 } 114 115 int fd = (int)SDL_GetNumberProperty(SDL_GetIOProperties(io), SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER, -1); 116 if (fd < 0) { 117 SDL_SetError("%s doesn't have SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER available", property); 118 return false; 119 } 120 121 *result = fd; 122 return true; 123} 124 125static bool AddFileDescriptorCloseActions(posix_spawn_file_actions_t *fa) 126{ 127 DIR *dir = opendir("/proc/self/fd"); 128 if (dir) { 129 struct dirent *entry; 130 while ((entry = readdir(dir)) != NULL) { 131 int fd = SDL_atoi(entry->d_name); 132 if (fd <= STDERR_FILENO) { 133 continue; 134 } 135 136 int flags = fcntl(fd, F_GETFD); 137 if (flags < 0 || (flags & FD_CLOEXEC)) { 138 continue; 139 } 140 if (posix_spawn_file_actions_addclose(fa, fd) != 0) { 141 closedir(dir); 142 return SDL_SetError("posix_spawn_file_actions_addclose failed: %s", strerror(errno)); 143 } 144 } 145 closedir(dir); 146 } else { 147 for (int fd = (int)(sysconf(_SC_OPEN_MAX) - 1); fd > STDERR_FILENO; --fd) { 148 int flags = fcntl(fd, F_GETFD); 149 if (flags < 0 || (flags & FD_CLOEXEC)) { 150 continue; 151 } 152 if (posix_spawn_file_actions_addclose(fa, fd) != 0) { 153 return SDL_SetError("posix_spawn_file_actions_addclose failed: %s", strerror(errno)); 154 } 155 } 156 } 157 return true; 158} 159 160bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID props) 161{ 162 char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL); 163 SDL_Environment *env = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, SDL_GetEnvironment()); 164 char **envp = NULL; 165 const char *working_directory = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING, NULL); 166 SDL_ProcessIO stdin_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_NULL); 167 SDL_ProcessIO stdout_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_INHERITED); 168 SDL_ProcessIO stderr_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_INHERITED); 169 bool redirect_stderr = SDL_GetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN, false) && 170 !SDL_HasProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER); 171 int stdin_pipe[2] = { -1, -1 }; 172 int stdout_pipe[2] = { -1, -1 }; 173 int stderr_pipe[2] = { -1, -1 }; 174 int fd = -1; 175 176 // Keep the malloc() before exec() so that an OOM won't run a process at all 177 envp = SDL_GetEnvironmentVariables(env); 178 if (!envp) { 179 return false; 180 } 181 182 SDL_ProcessData *data = SDL_calloc(1, sizeof(*data)); 183 if (!data) { 184 SDL_free(envp); 185 return false; 186 } 187 process->internal = data; 188 189 posix_spawnattr_t attr; 190 posix_spawn_file_actions_t fa; 191 192 if (posix_spawnattr_init(&attr) != 0) { 193 SDL_SetError("posix_spawnattr_init failed: %s", strerror(errno)); 194 goto posix_spawn_fail_none; 195 } 196 197 if (posix_spawn_file_actions_init(&fa) != 0) { 198 SDL_SetError("posix_spawn_file_actions_init failed: %s", strerror(errno)); 199 goto posix_spawn_fail_attr; 200 } 201 202 if (working_directory) { 203#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR 204#ifdef SDL_PLATFORM_APPLE 205 if (__builtin_available(macOS 10.15, *)) { 206 if (posix_spawn_file_actions_addchdir_np(&fa, working_directory) != 0) { 207 SDL_SetError("posix_spawn_file_actions_addchdir failed: %s", strerror(errno)); 208 goto posix_spawn_fail_all; 209 } 210 } else { 211 SDL_SetError("Setting the working directory is only supported on macOS 10.15 and newer"); 212 goto posix_spawn_fail_all; 213 } 214#else 215 if (posix_spawn_file_actions_addchdir(&fa, working_directory) != 0) { 216 SDL_SetError("posix_spawn_file_actions_addchdir failed: %s", strerror(errno)); 217 goto posix_spawn_fail_all; 218 } 219#endif // SDL_PLATFORM_APPLE 220#else 221 SDL_SetError("Setting the working directory is not supported"); 222 goto posix_spawn_fail_all; 223#endif 224 } 225 226 // Background processes don't have access to the terminal 227 if (process->background) { 228 if (stdin_option == SDL_PROCESS_STDIO_INHERITED) { 229 stdin_option = SDL_PROCESS_STDIO_NULL; 230 } 231 if (stdout_option == SDL_PROCESS_STDIO_INHERITED) { 232 stdout_option = SDL_PROCESS_STDIO_NULL; 233 } 234 if (stderr_option == SDL_PROCESS_STDIO_INHERITED) { 235 stderr_option = SDL_PROCESS_STDIO_NULL; 236 } 237 } 238 239 switch (stdin_option) { 240 case SDL_PROCESS_STDIO_REDIRECT: 241 if (!GetStreamFD(props, SDL_PROP_PROCESS_CREATE_STDIN_POINTER, &fd)) { 242 goto posix_spawn_fail_all; 243 } 244 if (posix_spawn_file_actions_adddup2(&fa, fd, STDIN_FILENO) != 0) { 245 SDL_SetError("posix_spawn_file_actions_adddup2 failed: %s", strerror(errno)); 246 goto posix_spawn_fail_all; 247 } 248 break; 249 case SDL_PROCESS_STDIO_APP: 250 if (!CreatePipe(stdin_pipe)) { 251 goto posix_spawn_fail_all; 252 } 253 if (posix_spawn_file_actions_adddup2(&fa, stdin_pipe[READ_END], STDIN_FILENO) != 0) { 254 SDL_SetError("posix_spawn_file_actions_adddup2 failed: %s", strerror(errno)); 255 goto posix_spawn_fail_all; 256 } 257 break; 258 case SDL_PROCESS_STDIO_NULL: 259 if (posix_spawn_file_actions_addopen(&fa, STDIN_FILENO, "/dev/null", O_RDONLY, 0) != 0) { 260 SDL_SetError("posix_spawn_file_actions_addopen failed: %s", strerror(errno)); 261 goto posix_spawn_fail_all; 262 } 263 break; 264 case SDL_PROCESS_STDIO_INHERITED: 265 default: 266 break; 267 } 268 269 switch (stdout_option) { 270 case SDL_PROCESS_STDIO_REDIRECT: 271 if (!GetStreamFD(props, SDL_PROP_PROCESS_CREATE_STDOUT_POINTER, &fd)) { 272 goto posix_spawn_fail_all; 273 } 274 if (posix_spawn_file_actions_adddup2(&fa, fd, STDOUT_FILENO) != 0) { 275 SDL_SetError("posix_spawn_file_actions_adddup2 failed: %s", strerror(errno)); 276 goto posix_spawn_fail_all; 277 } 278 break; 279 case SDL_PROCESS_STDIO_APP: 280 if (!CreatePipe(stdout_pipe)) { 281 goto posix_spawn_fail_all; 282 } 283 if (posix_spawn_file_actions_adddup2(&fa, stdout_pipe[WRITE_END], STDOUT_FILENO) != 0) { 284 SDL_SetError("posix_spawn_file_actions_adddup2 failed: %s", strerror(errno)); 285 goto posix_spawn_fail_all; 286 } 287 break; 288 case SDL_PROCESS_STDIO_NULL: 289 if (posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO, "/dev/null", O_WRONLY, 0644) != 0) { 290 SDL_SetError("posix_spawn_file_actions_addopen failed: %s", strerror(errno)); 291 goto posix_spawn_fail_all; 292 } 293 break; 294 case SDL_PROCESS_STDIO_INHERITED: 295 default: 296 break; 297 } 298 299 if (redirect_stderr) { 300 if (posix_spawn_file_actions_adddup2(&fa, STDOUT_FILENO, STDERR_FILENO) != 0) { 301 SDL_SetError("posix_spawn_file_actions_adddup2 failed: %s", strerror(errno)); 302 goto posix_spawn_fail_all; 303 } 304 } else { 305 switch (stderr_option) { 306 case SDL_PROCESS_STDIO_REDIRECT: 307 if (!GetStreamFD(props, SDL_PROP_PROCESS_CREATE_STDERR_POINTER, &fd)) { 308 goto posix_spawn_fail_all; 309 } 310 if (posix_spawn_file_actions_adddup2(&fa, fd, STDERR_FILENO) != 0) { 311 SDL_SetError("posix_spawn_file_actions_adddup2 failed: %s", strerror(errno)); 312 goto posix_spawn_fail_all; 313 } 314 break; 315 case SDL_PROCESS_STDIO_APP: 316 if (!CreatePipe(stderr_pipe)) { 317 goto posix_spawn_fail_all; 318 } 319 if (posix_spawn_file_actions_adddup2(&fa, stderr_pipe[WRITE_END], STDERR_FILENO) != 0) { 320 SDL_SetError("posix_spawn_file_actions_adddup2 failed: %s", strerror(errno)); 321 goto posix_spawn_fail_all; 322 } 323 break; 324 case SDL_PROCESS_STDIO_NULL: 325 if (posix_spawn_file_actions_addopen(&fa, STDERR_FILENO, "/dev/null", O_WRONLY, 0644) != 0) { 326 SDL_SetError("posix_spawn_file_actions_addopen failed: %s", strerror(errno)); 327 goto posix_spawn_fail_all; 328 } 329 break; 330 case SDL_PROCESS_STDIO_INHERITED: 331 default: 332 break; 333 } 334 } 335 336 if (!AddFileDescriptorCloseActions(&fa)) { 337 goto posix_spawn_fail_all; 338 } 339 340 // Spawn the new process 341 if (process->background) { 342 int status = -1; 343 #ifdef SDL_PLATFORM_APPLE // Apple has vfork marked as deprecated and (as of macOS 10.12) is almost identical to calling fork() anyhow. 344 const pid_t pid = fork(); 345 const char *forkname = "fork"; 346 #else 347 const pid_t pid = vfork(); 348 const char *forkname = "vfork"; 349 #endif 350 switch (pid) { 351 case -1: 352 SDL_SetError("%s() failed: %s", forkname, strerror(errno)); 353 goto posix_spawn_fail_all; 354 355 case 0: 356 // Detach from the terminal and launch the process 357 setsid(); 358 if (posix_spawnp(&data->pid, args[0], &fa, &attr, args, envp) != 0) { 359 _exit(errno); 360 } 361 _exit(0); 362 363 default: 364 if (waitpid(pid, &status, 0) < 0) { 365 SDL_SetError("waitpid() failed: %s", strerror(errno)); 366 goto posix_spawn_fail_all; 367 } 368 if (status != 0) { 369 SDL_SetError("posix_spawn() failed: %s", strerror(status)); 370 goto posix_spawn_fail_all; 371 } 372 break; 373 } 374 } else { 375 if (posix_spawnp(&data->pid, args[0], &fa, &attr, args, envp) != 0) { 376 SDL_SetError("posix_spawn() failed: %s", strerror(errno)); 377 goto posix_spawn_fail_all; 378 } 379 } 380 SDL_SetNumberProperty(process->props, SDL_PROP_PROCESS_PID_NUMBER, data->pid); 381 382 if (stdin_option == SDL_PROCESS_STDIO_APP) { 383 if (!SetupStream(process, stdin_pipe[WRITE_END], "wb", SDL_PROP_PROCESS_STDIN_POINTER)) { 384 close(stdin_pipe[WRITE_END]); 385 } 386 close(stdin_pipe[READ_END]); 387 } 388 389 if (stdout_option == SDL_PROCESS_STDIO_APP) { 390 if (!SetupStream(process, stdout_pipe[READ_END], "rb", SDL_PROP_PROCESS_STDOUT_POINTER)) { 391 close(stdout_pipe[READ_END]); 392 } 393 close(stdout_pipe[WRITE_END]); 394 } 395 396 if (stderr_option == SDL_PROCESS_STDIO_APP) { 397 if (!SetupStream(process, stderr_pipe[READ_END], "rb", SDL_PROP_PROCESS_STDERR_POINTER)) { 398 close(stderr_pipe[READ_END]); 399 } 400 close(stderr_pipe[WRITE_END]); 401 } 402 403 posix_spawn_file_actions_destroy(&fa); 404 posix_spawnattr_destroy(&attr); 405 SDL_free(envp); 406 407 return true; 408 409 /* --------------------------------------------------------------------- */ 410 411posix_spawn_fail_all: 412 posix_spawn_file_actions_destroy(&fa); 413 414posix_spawn_fail_attr: 415 posix_spawnattr_destroy(&attr); 416 417posix_spawn_fail_none: 418 if (stdin_pipe[READ_END] >= 0) { 419 close(stdin_pipe[READ_END]); 420 } 421 if (stdin_pipe[WRITE_END] >= 0) { 422 close(stdin_pipe[WRITE_END]); 423 } 424 if (stdout_pipe[READ_END] >= 0) { 425 close(stdout_pipe[READ_END]); 426 } 427 if (stdout_pipe[WRITE_END] >= 0) { 428 close(stdout_pipe[WRITE_END]); 429 } 430 if (stderr_pipe[READ_END] >= 0) { 431 close(stderr_pipe[READ_END]); 432 } 433 if (stderr_pipe[WRITE_END] >= 0) { 434 close(stderr_pipe[WRITE_END]); 435 } 436 SDL_free(envp); 437 return false; 438} 439 440bool SDL_SYS_KillProcess(SDL_Process *process, bool force) 441{ 442 int ret = kill(process->internal->pid, force ? SIGKILL : SIGTERM); 443 if (ret == 0) { 444 return true; 445 } else { 446 return SDL_SetError("Could not kill(): %s", strerror(errno)); 447 } 448} 449 450bool SDL_SYS_WaitProcess(SDL_Process *process, bool block, int *exitcode) 451{ 452 int wstatus = 0; 453 int ret; 454 pid_t pid = process->internal->pid; 455 456 if (process->background) { 457 // We can't wait on the status, so we'll poll to see if it's alive 458 if (block) { 459 while (kill(pid, 0) == 0) { 460 SDL_Delay(10); 461 } 462 } else { 463 if (kill(pid, 0) == 0) { 464 return false; 465 } 466 } 467 *exitcode = 0; 468 return true; 469 } else { 470 ret = waitpid(pid, &wstatus, block ? 0 : WNOHANG); 471 if (ret < 0) { 472 return SDL_SetError("Could not waitpid(): %s", strerror(errno)); 473 } 474 475 if (ret == 0) { 476 SDL_ClearError(); 477 return false; 478 } 479 480 if (WIFEXITED(wstatus)) { 481 *exitcode = WEXITSTATUS(wstatus); 482 } else if (WIFSIGNALED(wstatus)) { 483 *exitcode = -WTERMSIG(wstatus); 484 } else { 485 *exitcode = -255; 486 } 487 488 return true; 489 } 490} 491 492void SDL_SYS_DestroyProcess(SDL_Process *process) 493{ 494 SDL_IOStream *io; 495 496 io = (SDL_IOStream *)SDL_GetPointerProperty(process->props, SDL_PROP_PROCESS_STDIN_POINTER, NULL); 497 if (io) { 498 SDL_CloseIO(io); 499 } 500 501 io = (SDL_IOStream *)SDL_GetPointerProperty(process->props, SDL_PROP_PROCESS_STDOUT_POINTER, NULL); 502 if (io) { 503 SDL_CloseIO(io); 504 } 505 506 io = (SDL_IOStream *)SDL_GetPointerProperty(process->props, SDL_PROP_PROCESS_STDERR_POINTER, NULL); 507 if (io) { 508 SDL_CloseIO(io); 509 } 510 511 SDL_free(process->internal); 512} 513 514#endif // SDL_PROCESS_POSIX 515
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.