solidc
Robust collection of general-purpose cross-platform C libraries and data structures designed for rapid and safe development in C
Loading...
Searching...
No Matches
process.c
Go to the documentation of this file.
1
6#include "../include/process.h"
7#include "../include/file.h" // Required for INVALID_NATIVE_HANDLE
8#include "../include/macros.h"
9
10#include <errno.h>
11#include <stddef.h>
12
13#ifdef _WIN32
14#include <io.h> // for _access
15#define ACCESS _access
16#ifndef X_OK
17// Windows doesn't have X_OK, but MinGW does.
18#define X_OK 0
19#endif
20
21#define PATH_SEP ";" // Windows uses semicolon
22#define DIR_SEP "\\" // Windows directory separator
23#else
24#include <unistd.h> // for access
25#define ACCESS access
26#define PATH_SEP ":" // POSIX uses colon
27#define DIR_SEP "/" // POSIX directory separator
28#endif
29
30// Helper function to search for command in PATH
31static char* find_in_path(const char* command, const char* const* environment) {
32 if (!command) return NULL;
33
34#ifdef _WIN32
35 // Windows: Check for absolute path (C:\ or \\‍) or relative path (. or ..)
36 if ((command[0] != '\0' && command[1] == ':') || // C:\path
37 command[0] == '\\' || // \path or \\network
38 command[0] == '.') { // .\path or ..\path
39 return strdup(command);
40 }
41#else
42 // POSIX: Check for absolute or relative path
43 if (command[0] == '/' || command[0] == '.') {
44 return strdup(command);
45 }
46#endif
47
48 // Find PATH in environment
49 const char* path_env = NULL;
50 for (int i = 0; environment && environment[i]; i++) {
51 if (strncmp(environment[i], "PATH=", 5) == 0) {
52 path_env = environment[i] + 5;
53 break;
54 }
55 }
56
57 if (!path_env) {
58 return NULL; // No PATH in environment
59 }
60
61 // Search each PATH component
62 char* path_copy = strdup(path_env);
63 if (!path_copy) return NULL;
64
65 char* saveptr = NULL;
66 char* dir = strtok_r(path_copy, PATH_SEP, &saveptr);
67 char full_path[4096];
68
69 while (dir) {
70 snprintf(full_path, sizeof(full_path), "%s%s%s", dir, DIR_SEP, command);
71#ifdef _WIN32
72 // Windows: Try with and without .exe extension
73 if (ACCESS(full_path, X_OK) == 0) {
74 free(path_copy);
75 return strdup(full_path);
76 }
77
78 // Try adding .exe extension
79 char exe_path[4096];
80 snprintf(exe_path, sizeof(exe_path), "%s.exe", full_path);
81
82 if (ACCESS(exe_path, X_OK) == 0) {
83 free(path_copy);
84 return strdup(exe_path);
85 }
86#else
87 if (ACCESS(full_path, X_OK) == 0) {
88 free(path_copy);
89 return strdup(full_path);
90 }
91#endif
92
93 dir = strtok_r(NULL, PATH_SEP, &saveptr);
94 }
95
96 free(path_copy);
97 return NULL;
98}
99
100/* Platform-specific implementations of process and pipe handles */
101#ifdef _WIN32
102struct ProcessHandle {
103 PROCESS_INFORMATION process_info;
104 bool detached;
105};
106
107#else
108struct ProcessHandle {
109 pid_t pid;
110 bool detached;
111};
112#endif
113
114struct PipeHandle {
115 PipeFd read_fd;
116 PipeFd write_fd;
117 bool read_closed;
118 bool write_closed;
119};
120
121// New structure to represent file redirection
122struct FileRedirection {
123 int fd; // File descriptor
124 bool close_on_exec; // Whether to close on exec
125};
126
127/* Default options for process creation */
128static const ProcessOptions DEFAULT_OPTIONS = {
129 .working_directory = NULL,
130 .inherit_environment = true,
131 .environment = NULL,
132 .detached = false,
133 .io =
134 {
135 .stdin_pipe = NULL,
136 .stdout_pipe = NULL,
137 .stderr_pipe = NULL,
138 .merge_stderr = false,
139 },
140};
141
142/* Error handling helper functions */
143ProcessError process_system_error(void) {
144#ifdef _WIN32
145 DWORD error = GetLastError();
146 switch (error) {
147 case ERROR_INVALID_PARAMETER:
148 return PROCESS_ERROR_INVALID_ARGUMENT;
149 case ERROR_NOT_ENOUGH_MEMORY:
150 return PROCESS_ERROR_MEMORY;
151 case ERROR_ACCESS_DENIED:
152 return PROCESS_ERROR_PERMISSION_DENIED;
153 case ERROR_BROKEN_PIPE:
154 case ERROR_PIPE_BUSY:
155 case ERROR_PIPE_NOT_CONNECTED:
156 return PROCESS_ERROR_IO;
157 default:
158 return PROCESS_ERROR_UNKNOWN;
159 }
160#else
161 switch (errno) {
162 case EINVAL:
163 return PROCESS_ERROR_INVALID_ARGUMENT;
164 case ENOMEM:
165 return PROCESS_ERROR_MEMORY;
166 case EACCES:
167 case EPERM:
168 return PROCESS_ERROR_PERMISSION_DENIED;
169 case EBADF:
170 case EPIPE:
171 return PROCESS_ERROR_IO;
172 case ECHILD:
173 return PROCESS_ERROR_WAIT_FAILED;
174 default:
175 return PROCESS_ERROR_UNKNOWN;
176 }
177#endif
178}
179
183bool pipe_read_closed(PipeHandle* handle) { return handle->read_closed; }
184
188bool pipe_write_closed(PipeHandle* handle) { return handle->write_closed; }
189
193PipeFd pipe_read_fd(PipeHandle* handle) { return handle->read_fd; }
194
198PipeFd pipe_write_fd(PipeHandle* handle) { return handle->write_fd; }
199
200/* String descriptions for error codes */
202 switch (error) {
203 case PROCESS_SUCCESS:
204 return "Success";
205 case PROCESS_ERROR_INVALID_ARGUMENT:
206 return "Invalid argument";
207 case PROCESS_ERROR_FORK_FAILED:
208 return "Fork failed";
209 case PROCESS_ERROR_EXEC_FAILED:
210 return "Exec failed";
211 case PROCESS_ERROR_PIPE_FAILED:
212 return "Pipe creation failed";
213 case PROCESS_ERROR_MEMORY:
214 return "Memory allocation failed";
215 case PROCESS_ERROR_WAIT_FAILED:
216 return "Wait for process failed";
217 case PROCESS_ERROR_KILL_FAILED:
218 return "Failed to terminate process";
219 case PROCESS_ERROR_TERMINATE_FAILED:
220 return "Failed to terminate process";
221 case PROCESS_ERROR_PERMISSION_DENIED:
222 return "Permission denied";
223 case PROCESS_ERROR_IO:
224 return "I/O error";
225 case PROCESS_ERROR_TIMEOUT:
226 return "Operation timed out";
227 case PROCESS_ERROR_WOULD_BLOCK:
228 return "Operation would block (no data available)";
229 case PROCESS_ERROR_PIPE_CLOSED:
230 return "Pipe was closed";
231 case PROCESS_ERROR_UNKNOWN:
232 return "Unknown error";
233 default:
234 return "Invalid error code";
235 }
236}
237
238/* Implementation of the pipe API */
239ProcessError pipe_create(PipeHandle** pipeHandle) {
240 if (!pipeHandle) {
241 return PROCESS_ERROR_INVALID_ARGUMENT;
242 }
243
244 *pipeHandle = (PipeHandle*)calloc(1, sizeof(PipeHandle));
245 if (!*pipeHandle) {
246 return PROCESS_ERROR_MEMORY;
247 }
248
249#ifdef _WIN32
250 SECURITY_ATTRIBUTES security_attrs;
251 memset(&security_attrs, 0, sizeof(security_attrs));
252 security_attrs.nLength = sizeof(security_attrs);
253 security_attrs.bInheritHandle = TRUE;
254
255 if (!CreatePipe(&(*pipeHandle)->read_fd, &(*pipeHandle)->write_fd, &security_attrs, 0)) {
256 free(*pipeHandle);
257 *pipeHandle = NULL;
258 return PROCESS_ERROR_PIPE_FAILED;
259 }
260#else
261 int fds[2];
262 if (pipe(fds) != 0) {
263 free(*pipeHandle);
264 *pipeHandle = NULL;
265 return PROCESS_ERROR_PIPE_FAILED;
266 }
267
268 (*pipeHandle)->read_fd = fds[0];
269 (*pipeHandle)->write_fd = fds[1];
270#endif
271
272 return PROCESS_SUCCESS;
273}
274
282ProcessError pipe_set_nonblocking(PipeHandle* pipe, bool nonblocking) {
283 if (!pipe) {
284 return PROCESS_ERROR_INVALID_ARGUMENT;
285 }
286
287#ifdef _WIN32
288 DWORD mode = nonblocking ? PIPE_NOWAIT : PIPE_WAIT;
289 if (!SetNamedPipeHandleState(pipe->read_fd, &mode, NULL, NULL)) {
290 return process_system_error();
291 }
292 if (!SetNamedPipeHandleState(pipe->write_fd, &mode, NULL, NULL)) {
293 return process_system_error();
294 }
295#else
296 int flags;
297
298 // Set read end
299 flags = fcntl(pipe->read_fd, F_GETFL);
300 if (flags == -1) {
301 return process_system_error();
302 }
303
304 if (nonblocking) {
305 flags |= O_NONBLOCK;
306 } else {
307 flags &= ~O_NONBLOCK;
308 }
309
310 if (fcntl(pipe->read_fd, F_SETFL, flags) == -1) {
311 return process_system_error();
312 }
313
314 // Set write end
315 flags = fcntl(pipe->write_fd, F_GETFL);
316 if (flags == -1) {
317 return process_system_error();
318 }
319
320 if (nonblocking) {
321 flags |= O_NONBLOCK;
322 } else {
323 flags &= ~O_NONBLOCK;
324 }
325
326 if (fcntl(pipe->write_fd, F_SETFL, flags) == -1) {
327 return process_system_error();
328 }
329#endif
330
331 return PROCESS_SUCCESS;
332}
333
334ProcessError pipe_read(PipeHandle* pipe, void* buffer, size_t size, size_t* bytes_read, int timeout_ms) {
335 if (!pipe || !buffer || pipe->read_closed) {
336 return PROCESS_ERROR_INVALID_ARGUMENT;
337 }
338
339 if (bytes_read) {
340 *bytes_read = 0;
341 }
342
343#ifdef _WIN32
344 // Windows implementation remains similar but with better error codes
345 DWORD bytes_read_win = 0;
346 OVERLAPPED overlapped;
347 memset(&overlapped, 0, sizeof(overlapped));
348 overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
349
350 if (!overlapped.hEvent) {
351 return process_system_error();
352 }
353
354 if (!ReadFile(pipe->read_fd, buffer, (DWORD)size, NULL, &overlapped)) {
355 DWORD error = GetLastError();
356 if (error != ERROR_IO_PENDING) {
357 CloseHandle(overlapped.hEvent);
358 if (error == ERROR_BROKEN_PIPE) {
359 return PROCESS_ERROR_PIPE_CLOSED;
360 }
361 return process_system_error();
362 }
363 }
364
365 DWORD wait_result = WaitForSingleObject(overlapped.hEvent, timeout_ms < 0 ? INFINITE : (DWORD)timeout_ms);
366
367 if (wait_result == WAIT_OBJECT_0) {
368 if (!GetOverlappedResult(pipe->read_fd, &overlapped, &bytes_read_win, FALSE)) {
369 CloseHandle(overlapped.hEvent);
370 DWORD error = GetLastError();
371 if (error == ERROR_BROKEN_PIPE) {
372 return PROCESS_ERROR_PIPE_CLOSED;
373 }
374 return process_system_error();
375 }
376 if (bytes_read) {
377 *bytes_read = bytes_read_win;
378 }
379 } else if (wait_result == WAIT_TIMEOUT) {
380 CancelIo(pipe->read_fd);
381 CloseHandle(overlapped.hEvent);
382 // Map 0ms timeout to WOULDBLOCK for consistency with non-blocking reads
383 return (timeout_ms == 0) ? PROCESS_ERROR_WOULD_BLOCK : PROCESS_ERROR_TIMEOUT;
384 } else {
385 CloseHandle(overlapped.hEvent);
386 return process_system_error();
387 }
388
389 CloseHandle(overlapped.hEvent);
390#else
391 // posix implementation
392 if (timeout_ms >= 0) {
393 fd_set read_fds;
394 FD_ZERO(&read_fds);
395 FD_SET(pipe->read_fd, &read_fds);
396
397 struct timeval timeout;
398 timeout.tv_sec = timeout_ms / 1000;
399 timeout.tv_usec = (long)((timeout_ms % 1000) * 1000);
400
401 // select returns: -1 (error), 0 (timeout), >0 (ready)
402 int select_result = select(pipe->read_fd + 1, &read_fds, NULL, NULL, &timeout);
403
404 if (select_result == -1) {
405 if (errno == EINTR)
406 return PROCESS_ERROR_WOULD_BLOCK; // Retry logic usually handles this, but here we return
407 return process_system_error();
408 } else if (select_result == 0) {
409 // If timeout was 0, this means "Would Block".
410 // If timeout was > 0, this means "Timed Out".
411 return (timeout_ms == 0) ? PROCESS_ERROR_WOULD_BLOCK : PROCESS_ERROR_TIMEOUT;
412 }
413 // If select_result > 0, data is ready, proceed to read()
414 }
415
416 ssize_t result = read(pipe->read_fd, buffer, size);
417 if (result < 0) {
418 if (errno == EAGAIN || errno == EWOULDBLOCK) {
419 // Non-blocking read with no data available
420 return PROCESS_ERROR_WOULD_BLOCK;
421 } else if (errno == EPIPE) {
422 // Pipe was closed/broken
423 return PROCESS_ERROR_PIPE_CLOSED;
424 } else if (errno == EBADF) {
425 // Invalid file descriptor
426 return PROCESS_ERROR_PIPE_CLOSED;
427 }
428 return process_system_error();
429 }
430
431 if (result == 0) {
432 // EOF - pipe was closed on the write end
433 return PROCESS_ERROR_PIPE_CLOSED;
434 }
435
436 if (bytes_read) {
437 *bytes_read = (size_t)result;
438 }
439#endif
440
441 return PROCESS_SUCCESS;
442}
443
444ProcessError pipe_write(PipeHandle* pipe, const void* buffer, size_t size, size_t* bytes_written, int timeout_ms) {
445 if (!pipe || !buffer || pipe->write_closed) {
446 return PROCESS_ERROR_INVALID_ARGUMENT;
447 }
448
449 if (bytes_written) {
450 *bytes_written = 0;
451 }
452
453#ifdef _WIN32
454 DWORD bytes_written_win = 0;
455 OVERLAPPED overlapped;
456 memset(&overlapped, 0, sizeof(overlapped));
457 overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
458
459 if (!overlapped.hEvent) {
460 return process_system_error();
461 }
462
463 if (!WriteFile(pipe->write_fd, buffer, (DWORD)size, NULL, &overlapped)) {
464 DWORD error = GetLastError();
465 if (error != ERROR_IO_PENDING) {
466 CloseHandle(overlapped.hEvent);
467 if (error == ERROR_BROKEN_PIPE || error == ERROR_NO_DATA) {
468 return PROCESS_ERROR_PIPE_CLOSED;
469 }
470 return process_system_error();
471 }
472 }
473
474 DWORD wait_result = WaitForSingleObject(overlapped.hEvent, timeout_ms < 0 ? INFINITE : (DWORD)timeout_ms);
475
476 if (wait_result == WAIT_OBJECT_0) {
477 if (!GetOverlappedResult(pipe->write_fd, &overlapped, &bytes_written_win, FALSE)) {
478 CloseHandle(overlapped.hEvent);
479 DWORD error = GetLastError();
480 if (error == ERROR_BROKEN_PIPE || error == ERROR_NO_DATA) {
481 return PROCESS_ERROR_PIPE_CLOSED;
482 }
483 return process_system_error();
484 }
485 if (bytes_written) {
486 *bytes_written = bytes_written_win;
487 }
488 } else if (wait_result == WAIT_TIMEOUT) {
489 CancelIo(pipe->write_fd);
490 CloseHandle(overlapped.hEvent);
491 return PROCESS_ERROR_TIMEOUT;
492 } else {
493 CloseHandle(overlapped.hEvent);
494 return process_system_error();
495 }
496
497 CloseHandle(overlapped.hEvent);
498#else
499 if (timeout_ms >= 0) {
500 // For non-zero timeout, we need to use select
501 fd_set write_fds;
502 FD_ZERO(&write_fds);
503 FD_SET(pipe->write_fd, &write_fds);
504
505 struct timeval timeout;
506 struct timeval* timeout_ptr = NULL;
507 timeout.tv_sec = timeout_ms / 1000;
508 timeout.tv_usec = ((long)timeout_ms % 1000) * 1000;
509 timeout_ptr = &timeout;
510 int select_result = select(pipe->write_fd + 1, NULL, &write_fds, NULL, timeout_ptr);
511 if (select_result == -1) {
512 if (errno == EINTR)
513 return PROCESS_ERROR_WOULD_BLOCK; // Retry logic usually handles this, but here we return
514 return process_system_error();
515 } else if (select_result == 0) {
516 // If timeout was 0, this means "Would Block".
517 // If timeout was > 0, this means "Timed Out".
518 return (timeout_ms == 0) ? PROCESS_ERROR_WOULD_BLOCK : PROCESS_ERROR_TIMEOUT;
519 }
520 // If select_result > 0, data is ready, proceed to read()
521 }
522
523 ssize_t result = write(pipe->write_fd, buffer, size);
524 if (result < 0) {
525 if (errno == EAGAIN || errno == EWOULDBLOCK) {
526 // Non-blocking write - buffer full, no space available
527 return PROCESS_ERROR_WOULD_BLOCK;
528 } else if (errno == EPIPE) {
529 // Broken pipe - read end was closed
530 return PROCESS_ERROR_PIPE_CLOSED;
531 } else if (errno == EBADF) {
532 // Invalid file descriptor
533 return PROCESS_ERROR_PIPE_CLOSED;
534 }
535 return process_system_error();
536 }
537
538 if (bytes_written) {
539 *bytes_written = (size_t)result;
540 }
541#endif
542
543 return PROCESS_SUCCESS;
544}
545
546void pipe_close(PipeHandle* pipe) {
547 if (!pipe) {
548 return;
549 }
550
551#ifdef _WIN32
552 if (pipe->read_fd != INVALID_NATIVE_HANDLE && !pipe->read_closed) {
553 CloseHandle(pipe->read_fd);
554 pipe->read_closed = true;
555 pipe->read_fd = INVALID_NATIVE_HANDLE;
556 }
557 if (pipe->write_fd != INVALID_NATIVE_HANDLE && !pipe->write_closed) {
558 CloseHandle(pipe->write_fd);
559 pipe->write_closed = true;
560 pipe->write_fd = INVALID_NATIVE_HANDLE;
561 }
562#else
563 if (pipe->read_fd != INVALID_NATIVE_HANDLE && !pipe->read_closed) {
564 close(pipe->read_fd);
565 pipe->read_closed = true;
566 pipe->read_fd = INVALID_NATIVE_HANDLE;
567 }
568 if (pipe->write_fd != INVALID_NATIVE_HANDLE && !pipe->write_closed) {
569 close(pipe->write_fd);
570 pipe->write_closed = true;
571 pipe->write_fd = INVALID_NATIVE_HANDLE;
572 }
573#endif
574
575 free(pipe);
576}
577
578ProcessError pipe_close_read_end(PipeHandle* pipe) {
579 if (!pipe) return PROCESS_ERROR_INVALID_ARGUMENT;
580 if (pipe->read_fd != INVALID_NATIVE_HANDLE && !pipe->read_closed) {
581#ifdef _WIN32
582 CloseHandle(pipe->read_fd);
583#else
584 close(pipe->read_fd);
585#endif
586 pipe->read_closed = true;
587 pipe->read_fd = INVALID_NATIVE_HANDLE;
588 }
589 return PROCESS_SUCCESS;
590}
591
592ProcessError pipe_close_write_end(PipeHandle* pipe) {
593 if (!pipe) return PROCESS_ERROR_INVALID_ARGUMENT;
594 if (pipe->write_fd != INVALID_NATIVE_HANDLE && !pipe->write_closed) {
595#ifdef _WIN32
596 CloseHandle(pipe->write_fd);
597#else
598 close(pipe->write_fd);
599#endif
600 pipe->write_closed = true;
601 pipe->write_fd = INVALID_NATIVE_HANDLE;
602 }
603 return PROCESS_SUCCESS;
604}
605
606/* Implementation of the process API */
615#ifdef _WIN32
616
626static void append_escaped_win32_arg(char* dest, const char* arg) {
627 /* Fast path: nothing that needs quoting */
628 if (*arg != '\0' && !strpbrk(arg, " \t\n\v\"")) {
629 strcat(dest, arg);
630 return;
631 }
632
633 strcat(dest, "\"");
634
635 for (const char* p = arg; *p != '\0';) {
636 /* Count consecutive backslashes */
637 int num_bs = 0;
638 while (*p == '\\') {
639 num_bs++;
640 p++;
641 }
642
643 if (*p == '\0') {
644 /* Trailing backslashes: double them before the closing quote */
645 for (int k = 0; k < num_bs * 2; k++) strcat(dest, "\\");
646 break;
647 } else if (*p == '"') {
648 /* Backslashes before a quote: double them, then escape the quote */
649 for (int k = 0; k < num_bs * 2 + 1; k++) strcat(dest, "\\");
650 strcat(dest, "\"");
651 p++;
652 } else {
653 /* Literal backslashes followed by a normal char */
654 for (int k = 0; k < num_bs; k++) strcat(dest, "\\");
655 size_t len = strlen(dest);
656 dest[len] = *p;
657 dest[len + 1] = '\0';
658 p++;
659 }
660 }
661
662 strcat(dest, "\"");
663}
664
665static ProcessError win32_create_process(ProcessHandle** handle, const char* command, const char* const argv[],
666 const ProcessOptions* options) {
667 /* Calculate an upper-bound for the command-line buffer.
668 * Each character can expand to at most 2 (backslash doubling) plus
669 * 2 surrounding quotes + 1 space separator. */
670 size_t cmdline_len = 0;
671 int arg_count = 0;
672
673 while (argv[arg_count] != NULL) {
674 cmdline_len += strlen(argv[arg_count]) * 2 + 4; /* worst-case escaping */
675 arg_count++;
676 }
677 if (arg_count == 0) {
678 return PROCESS_ERROR_INVALID_ARGUMENT;
679 }
680
681 char* cmdline = (char*)malloc(cmdline_len + 1);
682 if (!cmdline) {
683 return PROCESS_ERROR_MEMORY;
684 }
685 cmdline[0] = '\0';
686
687 for (int i = 0; i < arg_count; i++) {
688 if (i > 0) strcat(cmdline, " ");
689 append_escaped_win32_arg(cmdline, argv[i]);
690 }
691
692 /* Prepare startup info with redirections */
693 STARTUPINFOA startup_info;
694 memset(&startup_info, 0, sizeof(startup_info));
695 startup_info.cb = sizeof(startup_info);
696 startup_info.dwFlags = STARTF_USESTDHANDLES;
697 startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
698 startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
699 startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
700
701 if (options->io.stdin_pipe) startup_info.hStdInput = options->io.stdin_pipe->read_fd;
702 if (options->io.stdout_pipe) startup_info.hStdOutput = options->io.stdout_pipe->write_fd;
703
704 if (options->io.stderr_pipe) {
705 startup_info.hStdError = options->io.stderr_pipe->write_fd;
706 } else if (options->io.merge_stderr) {
707 startup_info.hStdError = startup_info.hStdOutput;
708 }
709
710 DWORD creation_flags = 0;
711 if (options->detached) {
712 creation_flags |= DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP;
713 }
714
715 PROCESS_INFORMATION process_info;
716 BOOL success = CreateProcessA(command, cmdline, NULL, NULL, TRUE, creation_flags, (LPVOID)(options->environment),
717 options->working_directory, &startup_info, &process_info);
718
719 free(cmdline);
720
721 if (!success) {
722 return process_system_error();
723 }
724
725 *handle = (ProcessHandle*)malloc(sizeof(ProcessHandle));
726 if (!*handle) {
727 CloseHandle(process_info.hProcess);
728 CloseHandle(process_info.hThread);
729 return PROCESS_ERROR_MEMORY;
730 }
731
732 (*handle)->process_info = process_info;
733 (*handle)->detached = options->detached;
734
735 return PROCESS_SUCCESS;
736}
737#else
738static ProcessError unix_create_process(ProcessHandle** handle, const char* command, const char* const argv[],
739 const ProcessOptions* options) {
740 // Create pipes for redirection if needed
741 int stdin_pipe[2] = {-1, -1};
742 int stdout_pipe[2] = {-1, -1};
743 int stderr_pipe[2] = {-1, -1};
744
745 // Set up pipes for redirections
746 if (options->io.stdin_pipe) {
747 stdin_pipe[0] = options->io.stdin_pipe->read_fd;
748 stdin_pipe[1] = options->io.stdin_pipe->write_fd;
749 }
750
751 if (options->io.stdout_pipe) {
752 stdout_pipe[0] = options->io.stdout_pipe->read_fd;
753 stdout_pipe[1] = options->io.stdout_pipe->write_fd;
754 }
755
756 if (options->io.stderr_pipe) {
757 stderr_pipe[0] = options->io.stderr_pipe->read_fd;
758 stderr_pipe[1] = options->io.stderr_pipe->write_fd;
759 }
760
761 // Fork the process
762 pid_t pid = fork();
763
764 if (pid < 0) {
765 return PROCESS_ERROR_FORK_FAILED;
766 }
767
768 if (pid == 0) {
769 // Child process
770
771 // Handle working directory
772 if (options->working_directory) {
773 if (chdir(options->working_directory) != 0) {
774 _exit(127);
775 }
776 }
777
778 // Handle standard input
779 if (options->io.stdin_pipe) {
780 dup2(stdin_pipe[0], STDIN_FILENO);
781 close(stdin_pipe[0]);
782 close(stdin_pipe[1]);
783 }
784
785 // Handle standard output
786 if (options->io.stdout_pipe) {
787 dup2(stdout_pipe[1], STDOUT_FILENO);
788 close(stdout_pipe[0]);
789 close(stdout_pipe[1]);
790 }
791
792 // Handle standard error
793 if (options->io.stderr_pipe) {
794 dup2(stderr_pipe[1], STDERR_FILENO);
795 close(stderr_pipe[0]);
796 close(stderr_pipe[1]);
797 } else if (options->io.merge_stderr) {
798 dup2(STDOUT_FILENO, STDERR_FILENO);
799 }
800
801 // Detach from parent if requested
802 if (options->detached) {
803 if (setsid() < 0) {
804 _exit(127);
805 }
806 }
807
808 // Execute the command
809 if (options->inherit_environment) {
810 execvp(command, (char* const*)argv);
811 } else {
812 // Custom or empty environment - need to search PATH manually
813 const char* const* env = options->environment ? options->environment : (const char* const[]){NULL};
814 char* cmd_path = find_in_path(command, env);
815 if (cmd_path) {
816 execve(cmd_path, (char* const*)argv, (char* const*)env);
817 free(cmd_path);
818 } else {
819 // Try command as-is (might be absolute path)
820 execve(command, (char* const*)argv, (char* const*)env);
821 }
822 }
823
824 // If we get here, exec failed
825 perror("execve");
826 _exit(127);
827 }
828
829 // Parent process
830 *handle = (ProcessHandle*)malloc(sizeof(ProcessHandle));
831 if (!*handle) {
832 return PROCESS_ERROR_MEMORY;
833 }
834
835 (*handle)->pid = pid;
836 (*handle)->detached = options->detached;
837
838 return PROCESS_SUCCESS;
839}
840#endif
841
842ProcessError process_create(ProcessHandle** handle, const char* command, const char* const argv[],
843 const ProcessOptions* options) {
844 if (!handle || !command || !argv || !argv[0]) {
845 return PROCESS_ERROR_INVALID_ARGUMENT;
846 }
847
848 // Use default options if not provided
849 ProcessOptions effective_options;
850 if (options) {
851 effective_options = *options;
852 } else {
853 effective_options = DEFAULT_OPTIONS;
854 }
855
856#ifdef _WIN32
857 return win32_create_process(handle, command, argv, &effective_options);
858#else
859 return unix_create_process(handle, command, argv, &effective_options);
860#endif
861}
862
868void process_free(ProcessHandle* handle) {
869 if (!handle) return;
870 free(handle);
871}
872
873#ifndef _WIN32
874static inline void set_process_result(int status, ProcessResult* result) {
875 if (WIFEXITED(status)) {
876 result->exit_code = WEXITSTATUS(status);
877 result->exited_normally = true;
878 } else if (WIFSIGNALED(status)) {
879 result->exit_code = WTERMSIG(status);
880 result->exited_normally = false;
881 result->term_signal = WTERMSIG(status); // only set term_signal here
882 } else {
883 result->exit_code = -1; // Undefined exit reason
884 result->exited_normally = false;
885 }
886}
887#endif
888
889// Cross-platform nanosleep function
890void NANOSLEEP(long seconds, long nanoseconds) {
891#ifdef _WIN32
892 // On Windows, Sleep works in milliseconds,
893 // so we convert seconds and nanoseconds to milliseconds
894 unsigned long total_milliseconds = (unsigned)(seconds * 1000 + nanoseconds / 1000000);
895 Sleep(total_milliseconds);
896#else
897 // On Linux/Unix, we can use nanosleep directly
898 struct timespec req;
899 req.tv_sec = seconds;
900 req.tv_nsec = nanoseconds;
901 nanosleep(&req, NULL);
902#endif
903}
904
905ProcessError process_wait(ProcessHandle* handle, ProcessResult* result, int timeout_ms) {
906 if (!handle) {
907 return PROCESS_ERROR_INVALID_ARGUMENT;
908 }
909
910 if (handle->detached) {
911 // Cannot wait for detached processes
912 return PROCESS_ERROR_INVALID_ARGUMENT;
913 }
914
915#ifdef _WIN32
916 // Windows-specific code
917 DWORD wait_result =
918 WaitForSingleObject(handle->process_info.hProcess, timeout_ms < 0 ? INFINITE : (DWORD)timeout_ms);
919
920 if (wait_result == WAIT_TIMEOUT) {
921 return PROCESS_ERROR_WAIT_FAILED;
922 } else if (wait_result != WAIT_OBJECT_0) {
923 return process_system_error();
924 }
925
926 if (result) {
927 DWORD exit_code;
928 if (!GetExitCodeProcess(handle->process_info.hProcess, &exit_code)) {
929 return process_system_error();
930 }
931
932 result->exit_code = (int)exit_code;
933 result->exited_normally = true;
934 result->term_signal = 0; // No signal on Windows
935 }
936#else
937 // Linux/Unix-specific code
938 int status = 0;
939 pid_t wait_result = 0;
940
941 if (timeout_ms < 0) {
942 // Wait indefinitely
943 wait_result = waitpid(handle->pid, &status, 0);
944 } else {
945 // Wait with timeout using WNOHANG and a busy-wait loop for the timeout
946 int remaining_timeout_ms = timeout_ms;
947
948 // Here, we wait in a loop until the process exits or the timeout is reached
949 while ((wait_result = waitpid(handle->pid, &status, WNOHANG)) == 0 && remaining_timeout_ms > 0) {
950 struct timespec ts;
951 ts.tv_sec = remaining_timeout_ms / 1000;
952 ts.tv_nsec = (remaining_timeout_ms % 1000) * 1000000L;
953
954 // Sleep for the remaining timeout duration
955 NANOSLEEP(ts.tv_sec, ts.tv_nsec);
956
957 // Adjust remaining timeout
958 remaining_timeout_ms -= (int)((ts.tv_sec * 1000) + (ts.tv_nsec / 1000000));
959 }
960 }
961
962 if (wait_result < 0) {
963 return process_system_error();
964 }
965
966 if (wait_result == 0) {
967 // Process is still running
968 return PROCESS_ERROR_WAIT_FAILED;
969 }
970
971 if (result) {
972 set_process_result(status, result);
973 }
974
975#endif
976 return PROCESS_SUCCESS;
977}
978
987ProcessError process_terminate(ProcessHandle* handle, bool force) {
988 if (!handle) {
989 return PROCESS_ERROR_INVALID_ARGUMENT;
990 }
991#ifdef _WIN32
992 if (force) {
993 // Force termination using TerminateProcess (like SIGKILL)
994 if (!TerminateProcess(handle->process_info.hProcess, 1)) {
995 return PROCESS_ERROR_TERMINATE_FAILED;
996 }
997 } else {
998 // Graceful termination using GenerateConsoleCtrlEvent or other method
999 // GenerateCtrlEvent is typically used for console processes
1000 if (!GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) {
1001 return PROCESS_ERROR_TERMINATE_FAILED;
1002 }
1003 }
1004#else
1005 if (handle->pid <= 0) {
1006 return PROCESS_ERROR_INVALID_ARGUMENT;
1007 }
1008
1009 if (force) {
1010 // Force termination using SIGKILL (immediate termination)
1011 if (kill(handle->pid, SIGKILL) != 0) {
1012 return PROCESS_ERROR_KILL_FAILED;
1013 }
1014 } else {
1015 // Graceful termination using SIGTERM (request termination)
1016 if (kill(handle->pid, SIGTERM) != 0) {
1017 return PROCESS_ERROR_KILL_FAILED;
1018 }
1019 }
1020#endif
1021 return PROCESS_SUCCESS;
1022}
1023
1024ProcessError process_run_and_capture(const char* command, const char* const argv[], ProcessOptions* options,
1025 int* exit_code) {
1026 ProcessHandle* proc = NULL;
1027 ProcessError err = {0};
1028 err = process_create(&proc, command, argv, options);
1029 if (err != PROCESS_SUCCESS) {
1030 return err;
1031 }
1032
1033 ProcessResult res = {0};
1034 err = process_wait(proc, &res, -1);
1035 process_free(proc);
1036
1037 if (exit_code) {
1038 *exit_code = res.exit_code;
1039 }
1040
1041 if (err != PROCESS_SUCCESS) {
1042 return err;
1043 }
1044
1045 if (res.exit_code != 0) {
1046 return PROCESS_ERROR_EXEC_FAILED;
1047 }
1048 return PROCESS_SUCCESS;
1049}
1050
1051#ifndef _WIN32
1052// ======== Redirection ==================
1062ProcessError process_redirect_to_file(FileRedirection** redirection, const char* filepath, int flags,
1063 unsigned int mode) {
1064 if (!redirection || !filepath) {
1065 return PROCESS_ERROR_INVALID_ARGUMENT;
1066 }
1067
1068 *redirection = (FileRedirection*)malloc(sizeof(FileRedirection));
1069 if (!*redirection) {
1070 return PROCESS_ERROR_MEMORY;
1071 }
1072
1073 // Open the file with the specified flags and mode
1074 int fd = open(filepath, flags, mode);
1075 if (fd < 0) {
1076 free(*redirection);
1077 *redirection = NULL;
1078 return process_system_error();
1079 }
1080
1081 (*redirection)->fd = fd;
1082 (*redirection)->close_on_exec = true;
1083
1084 return PROCESS_SUCCESS;
1085}
1086
1095ProcessError process_redirect_to_fd(FileRedirection** redirection, int fd, bool close_on_exec) {
1096 if (!redirection || fd < 0) {
1097 return PROCESS_ERROR_INVALID_ARGUMENT;
1098 }
1099
1100 *redirection = (FileRedirection*)malloc(sizeof(FileRedirection));
1101 if (!*redirection) {
1102 return PROCESS_ERROR_MEMORY;
1103 }
1104
1105 (*redirection)->fd = fd;
1106 (*redirection)->close_on_exec = close_on_exec;
1107
1108 return PROCESS_SUCCESS;
1109}
1110
1116void process_close_redirection(FileRedirection* redirection) {
1117 if (!redirection) {
1118 return;
1119 }
1120
1121 if (redirection->close_on_exec && redirection->fd >= 0) {
1122 close(redirection->fd);
1123 }
1124
1125 free(redirection);
1126 redirection = NULL;
1127}
1128
1138ProcessError process_create_with_redirection(ProcessHandle** handle, const char* command, const char* const argv[],
1139 const ExtProcessOptions* options) {
1140 if (!handle || !command || !argv || !argv[0]) {
1141 return PROCESS_ERROR_INVALID_ARGUMENT;
1142 }
1143 // Fork the process
1144 pid_t pid = fork();
1145
1146 if (pid < 0) {
1147 return PROCESS_ERROR_FORK_FAILED;
1148 }
1149
1150 if (pid == 0) {
1151 // Child process
1152
1153 // Handle working directory
1154 if (options->working_directory) {
1155 if (chdir(options->working_directory) != 0) {
1156 _exit(127);
1157 }
1158 }
1159
1160 // Handle standard input
1161 if (options->io.stdin_pipe) {
1162 if (dup2(options->io.stdin_pipe->read_fd, STDIN_FILENO) == -1) {
1163 perror("dup2");
1164 return PROCESS_ERROR_IO;
1165 };
1166
1167 // Close pipe handles that aren't needed in child
1168 close(options->io.stdin_pipe->read_fd);
1169 close(options->io.stdin_pipe->write_fd);
1170 }
1171
1172 // Handle standard output
1173 if (options->io.stdout_pipe) {
1174 if (dup2(options->io.stdout_pipe->write_fd, STDOUT_FILENO) == -1) {
1175 perror("dup2");
1176 return PROCESS_ERROR_IO;
1177 };
1178 close(options->io.stdout_pipe->read_fd);
1179 close(options->io.stdout_pipe->write_fd);
1180 } else if (options->io.stdout_file) {
1181 // Redirect stdout to file
1182 if (dup2(options->io.stdout_file->fd, STDOUT_FILENO) == -1) {
1183 perror("dup2");
1184 return PROCESS_ERROR_IO;
1185 };
1186 if (options->io.stdout_file->close_on_exec) {
1187 close(options->io.stdout_file->fd);
1188 }
1189 }
1190
1191 // Handle standard error
1192 if (options->io.stderr_pipe) {
1193 if (dup2(options->io.stderr_pipe->write_fd, STDERR_FILENO) == -1) {
1194 perror("dup2");
1195 return PROCESS_ERROR_IO;
1196 };
1197 close(options->io.stderr_pipe->read_fd);
1198 close(options->io.stderr_pipe->write_fd);
1199 } else if (options->io.stderr_file) {
1200 // Redirect stderr to file
1201 if (dup2(options->io.stderr_file->fd, STDERR_FILENO) == -1) {
1202 perror("dup2");
1203 return PROCESS_ERROR_IO;
1204 };
1205 if (options->io.stderr_file->close_on_exec) {
1206 close(options->io.stderr_file->fd);
1207 }
1208 } else if (options->io.merge_stderr) {
1209 if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1) {
1210 perror("dup2");
1211 return PROCESS_ERROR_IO;
1212 };
1213 }
1214
1215 // Detach from parent if requested
1216 if (options->detached) {
1217 if (setsid() < 0) {
1218 _exit(127);
1219 }
1220 }
1221
1222 // Execute the command
1223 if (options->environment) {
1224 if (execve(command, (char* const*)argv, (char* const*)options->environment) == -1) {
1225 perror("execve");
1226 };
1227 } else if (options->inherit_environment) {
1228 if (execvp(command, (char* const*)argv) == -1) {
1229 perror("execvp");
1230 };
1231 } else {
1232 char* empty_env[] = {NULL};
1233 if (execve(command, (char* const*)argv, empty_env) == -1) {
1234 perror("execve");
1235 };
1236 }
1237
1238 // If we get here, exec failed
1239 _exit(127);
1240 }
1241
1242 // Parent process
1243 *handle = (ProcessHandle*)malloc(sizeof(ProcessHandle));
1244 if (!*handle) {
1245 return PROCESS_ERROR_MEMORY;
1246 }
1247
1248 (*handle)->pid = pid;
1249 (*handle)->detached = options->detached;
1250
1251 return PROCESS_SUCCESS;
1252}
1253
1254ProcessError process_run_with_multiwriter(ProcessResult* result, const char* cmd, const char* args[], int output_fds[],
1255 int error_fds[]) {
1256 // Create pipes for stdout and stderr
1257 int stdout_pipe[2];
1258 int stderr_pipe[2];
1259
1260 // Create a pipe for stdout
1261 if (pipe(stdout_pipe) < 0) {
1262 perror("pipe (stdout)");
1263 return PROCESS_ERROR_PIPE_FAILED;
1264 }
1265
1266 // Create a pipe for stderr
1267 if (pipe(stderr_pipe) < 0) {
1268 perror("pipe (stderr)");
1269 // Clean up stdout pipe if stderr pipe creation fails
1270 close(stdout_pipe[0]);
1271 close(stdout_pipe[1]);
1272 return PROCESS_ERROR_PIPE_FAILED;
1273 }
1274
1275 // Fork the child process that will execute the command
1276 pid_t cmd_pid = fork();
1277 if (cmd_pid < 0) {
1278 perror("fork");
1279 // Clean up pipes if fork fails
1280 close(stdout_pipe[0]);
1281 close(stdout_pipe[1]);
1282 close(stderr_pipe[0]);
1283 close(stderr_pipe[1]);
1284 return PROCESS_ERROR_FORK_FAILED;
1285 }
1286
1287 if (cmd_pid == 0) { // Command process
1288 // Close read ends of the pipes (not needed in the command process)
1289 close(stdout_pipe[0]);
1290 close(stderr_pipe[0]);
1291
1292 // Redirect stdout to the write end of the stdout pipe
1293 if (dup2(stdout_pipe[1], STDOUT_FILENO) < 0) {
1294 perror("dup2 (stdout)");
1295 exit(EXIT_FAILURE);
1296 }
1297
1298 // Redirect stderr to the write end of the stderr pipe
1299 if (dup2(stderr_pipe[1], STDERR_FILENO) < 0) {
1300 perror("dup2 (stderr)");
1301 exit(EXIT_FAILURE);
1302 }
1303
1304 // Close the write ends of the pipes (they are now duplicated to
1305 // stdout/stderr)
1306 close(stdout_pipe[1]);
1307 close(stderr_pipe[1]);
1308
1309 // Execute the command
1310 execv(cmd, (char* const*)args);
1311 // If execv fails, print an error and exit
1312 perror("execv");
1313 exit(EXIT_FAILURE);
1314 }
1315
1316 // Parent process
1317 // Close write ends of the pipes (not needed in the parent process)
1318 close(stdout_pipe[1]);
1319 close(stderr_pipe[1]);
1320
1321 int child_count = 1; // Start with 1 for the command process
1322
1323 // Fork a single tee process for stdout that writes to all output_fds
1324 pid_t stdout_tee_pid = fork();
1325 if (stdout_tee_pid < 0) {
1326 perror("fork tee (stdout)");
1327 // Clean up pipes and wait for the command process if fork fails
1328 close(stdout_pipe[0]);
1329 close(stderr_pipe[0]);
1330 waitpid(cmd_pid, NULL, 0); // Wait for the command process to avoid zombies
1331 return PROCESS_ERROR_FORK_FAILED;
1332 }
1333
1334 if (stdout_tee_pid == 0) { // Tee child process for stdout
1335 char buffer[4096];
1336 ssize_t n = 0;
1337
1338 // Read from the stdout pipe and write to all output_fds
1339 while ((n = read(stdout_pipe[0], buffer, sizeof(buffer))) > 0) {
1340 for (int i = 0; output_fds[i] != -1; i++) {
1341 if (write(output_fds[i], buffer, (size_t)n) < 0) {
1342 perror("write (stdout tee)");
1343 exit(EXIT_FAILURE);
1344 }
1345 }
1346 }
1347
1348 // Check for read errors
1349 if (n < 0) {
1350 perror("read (stdout tee)");
1351 exit(EXIT_FAILURE);
1352 }
1353
1354 exit(EXIT_SUCCESS);
1355 }
1356
1357 child_count++;
1358
1359 // Fork a single tee process for stderr that writes to all error_fds
1360 pid_t stderr_tee_pid = fork();
1361 if (stderr_tee_pid < 0) {
1362 perror("fork tee (stderr)");
1363 // Clean up pipes and wait for the command and stdout tee processes if fork
1364 // fails
1365 close(stdout_pipe[0]);
1366 close(stderr_pipe[0]);
1367 waitpid(cmd_pid, NULL, 0); // Wait for the command process
1368 waitpid(stdout_tee_pid, NULL, 0); // Wait for the stdout tee process
1369 return PROCESS_ERROR_FORK_FAILED;
1370 }
1371
1372 if (stderr_tee_pid == 0) { // Tee child process for stderr
1373 char buffer[4096];
1374 ssize_t n = 0;
1375
1376 // Read from the stderr pipe and write to all error_fds
1377 while ((n = read(stderr_pipe[0], buffer, sizeof(buffer))) > 0) {
1378 for (int i = 0; error_fds[i] != -1; i++) {
1379 if (write(error_fds[i], buffer, (size_t)n) < 0) {
1380 perror("write (stderr tee)");
1381 exit(EXIT_FAILURE);
1382 }
1383 }
1384 }
1385
1386 // Check for read errors
1387 if (n < 0) {
1388 perror("read (stderr tee)");
1389 exit(EXIT_FAILURE);
1390 }
1391
1392 exit(EXIT_SUCCESS);
1393 }
1394
1395 child_count++;
1396
1397 // Close read ends of the pipes after all tee processes are started
1398 close(stdout_pipe[0]);
1399 close(stderr_pipe[0]);
1400
1401 // Wait for the command process specifically to get its status
1402 int cmd_status = 0;
1403 waitpid(cmd_pid, &cmd_status, 0);
1404 set_process_result(cmd_status, result);
1405
1406 // Wait for all remaining child processes (stdout and stderr tee processes)
1407 while (child_count > 1) {
1408 wait(NULL);
1409 child_count--;
1410 }
1411
1412 // Check if the command process exited successfully
1413 if (WIFEXITED(cmd_status)) {
1414 return PROCESS_SUCCESS;
1415 } else {
1416 return PROCESS_ERROR_EXEC_FAILED;
1417 }
1418}
1419
1430ProcessError process_run_with_file_redirection(ProcessHandle** handle, const char* command, const char* const argv[],
1431 const char* stdout_file, const char* stderr_file, bool append) {
1432 ExtProcessOptions options;
1433 memset(&options, 0, sizeof(options));
1434 options.inherit_environment = true;
1435
1436 FileRedirection* stdout_redir = NULL;
1437 FileRedirection* stderr_redir = NULL;
1438 ProcessError err = PROCESS_SUCCESS;
1439
1440 if (stdout_file) {
1441 int flags = O_WRONLY | O_CREAT;
1442 flags |= (append ? O_APPEND : O_TRUNC);
1443
1444 err = process_redirect_to_file(&stdout_redir, stdout_file, flags, 0644);
1445 if (err != PROCESS_SUCCESS) {
1446 return err;
1447 }
1448 options.io.stdout_file = stdout_redir;
1449 }
1450
1451 if (stderr_file) {
1452 int flags = O_WRONLY | O_CREAT;
1453 flags |= (append ? O_APPEND : O_TRUNC);
1454
1455 err = process_redirect_to_file(&stderr_redir, stderr_file, flags, 0644);
1456 if (err != PROCESS_SUCCESS) {
1457 if (stdout_redir) {
1458 process_close_redirection(stdout_redir);
1459 }
1460 return err;
1461 }
1462 options.io.stderr_file = stderr_redir;
1463 }
1464
1465 // Create the process with redirection
1466 err = process_create_with_redirection(handle, command, argv, &options);
1467
1468 // Clean up redirections
1469 if (stdout_redir) {
1470 process_close_redirection(stdout_redir);
1471 }
1472
1473 if (stderr_redir) {
1474 process_close_redirection(stderr_redir);
1475 }
1476
1477 return err;
1478}
1479
1480#endif // Linux only
void process_close_redirection(FileRedirection *redirection)
Close and free a file redirection.
Definition process.c:1116
ProcessError process_redirect_to_fd(FileRedirection **redirection, int fd, bool close_on_exec)
Create a file redirection from an existing file descriptor.
Definition process.c:1095
ProcessError process_run_and_capture(const char *command, const char *const argv[], ProcessOptions *options, int *exit_code)
Run a command and capture its output.
Definition process.c:1024
ProcessError process_create(ProcessHandle **handle, const char *command, const char *const argv[], const ProcessOptions *options)
Create a new process When custom environment is provided, command must be an absolute path or relativ...
Definition process.c:842
ProcessError process_redirect_to_file(FileRedirection **redirection, const char *filepath, int flags, unsigned int mode)
Create a new file redirection for a process.
Definition process.c:1062
PipeFd pipe_read_fd(PipeHandle *handle)
Definition process.c:193
ProcessError process_terminate(ProcessHandle *handle, bool force)
Terminate a running process.
Definition process.c:987
ProcessError pipe_write(PipeHandle *pipe, const void *buffer, size_t size, size_t *bytes_written, int timeout_ms)
Write data to a pipe.
Definition process.c:444
ProcessError pipe_read(PipeHandle *pipe, void *buffer, size_t size, size_t *bytes_read, int timeout_ms)
Read data from a pipe.
Definition process.c:334
void process_free(ProcessHandle *handle)
Free resources associated with a process handle.
Definition process.c:868
ProcessError pipe_create(PipeHandle **pipeHandle)
Create a new pipe for IPC.
Definition process.c:239
ProcessError process_wait(ProcessHandle *handle, ProcessResult *result, int timeout_ms)
Wait for a process to complete.
Definition process.c:905
bool pipe_read_closed(PipeHandle *handle)
Definition process.c:183
ProcessError process_run_with_file_redirection(ProcessHandle **handle, const char *command, const char *const argv[], const char *stdout_file, const char *stderr_file, bool append)
Helper function to set up redirection to a file.
Definition process.c:1430
ProcessError process_run_with_multiwriter(ProcessResult *result, const char *cmd, const char *args[], int output_fds[], int error_fds[])
Run the command in a process that duplicates output to multiple destinations.
Definition process.c:1254
PipeFd pipe_write_fd(PipeHandle *handle)
Definition process.c:198
void pipe_close(PipeHandle *pipe)
Close a pipe.
Definition process.c:546
bool pipe_write_closed(PipeHandle *handle)
Definition process.c:188
ProcessError process_create_with_redirection(ProcessHandle **handle, const char *command, const char *const argv[], const ExtProcessOptions *options)
Create a process with extended redirection options.
Definition process.c:1138
const char * process_error_string(ProcessError error)
Get a string description of a process error.
Definition process.c:201
ProcessError pipe_set_nonblocking(PipeHandle *pipe, bool nonblocking)
Set non-blocking mode on a pipe.
Definition process.c:282
struct FileRedirection FileRedirection
Definition process.h:88
struct PipeHandle PipeHandle
Pipe handle type for IPC (platform-specific details hidden in implementation)
Definition process.h:83
struct ProcessHandle ProcessHandle
Process handle type (platform-specific details hidden in implementation)
Definition process.h:77
ProcessError
Error codes for process operations.
Definition process.h:44
Options for process creation.
Definition process.h:93
Result information after a process completes.
Definition process.h:134