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