1/*
2 The latest version of this library is available on GitHub;
3 https://github.com/sheredom/subprocess.h
4*/
5
6/*
7 This is free and unencumbered software released into the public domain.
8
9 Anyone is free to copy, modify, publish, use, compile, sell, or
10 distribute this software, either in source code form or as a compiled
11 binary, for any purpose, commercial or non-commercial, and by any
12 means.
13
14 In jurisdictions that recognize copyright laws, the author or authors
15 of this software dedicate any and all copyright interest in the
16 software to the public domain. We make this dedication for the benefit
17 of the public at large and to the detriment of our heirs and
18 successors. We intend this dedication to be an overt act of
19 relinquishment in perpetuity of all present and future rights to this
20 software under copyright law.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
25 IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
26 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
27 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
28 OTHER DEALINGS IN THE SOFTWARE.
29
30 For more information, please refer to <http://unlicense.org/>
31*/
32
33#ifndef SHEREDOM_SUBPROCESS_H_INCLUDED
34#define SHEREDOM_SUBPROCESS_H_INCLUDED
35
36#if defined(_MSC_VER)
37#pragma warning(push, 1)
38
39/* disable warning: '__cplusplus' is not defined as a preprocessor macro,
40 * replacing with '0' for '#if/#elif' */
41#pragma warning(disable : 4668)
42#endif
43
44#include <stdio.h>
45#include <string.h>
46
47#if defined(_MSC_VER)
48#pragma warning(pop)
49#endif
50
51#if defined(__TINYC__)
52#define SUBPROCESS_ATTRIBUTE(a) __attribute((a))
53#else
54#define SUBPROCESS_ATTRIBUTE(a) __attribute__((a))
55#endif
56
57#if defined(_MSC_VER)
58#define subprocess_pure
59#define subprocess_weak __inline
60#define subprocess_tls __declspec(thread)
61#elif defined(__MINGW32__)
62#define subprocess_pure SUBPROCESS_ATTRIBUTE(pure)
63#define subprocess_weak static SUBPROCESS_ATTRIBUTE(used)
64#define subprocess_tls __thread
65#elif defined(__clang__) || defined(__GNUC__) || defined(__TINYC__)
66#define subprocess_pure SUBPROCESS_ATTRIBUTE(pure)
67#define subprocess_weak SUBPROCESS_ATTRIBUTE(weak)
68#define subprocess_tls __thread
69#else
70#error Non clang, non gcc, non MSVC compiler found!
71#endif
72
73struct subprocess_s;
74
75enum subprocess_option_e {
76 // stdout and stderr are the same FILE.
77 subprocess_option_combined_stdout_stderr = 0x1,
78
79 // The child process should inherit the environment variables of the parent.
80 subprocess_option_inherit_environment = 0x2,
81
82 // Enable asynchronous reading of stdout/stderr before it has completed.
83 subprocess_option_enable_async = 0x4,
84
85 // Enable the child process to be spawned with no window visible if supported
86 // by the platform.
87 subprocess_option_no_window = 0x8,
88
89 // Search for program names in the PATH variable. Always enabled on Windows.
90 // Note: this will **not** search for paths in any provided custom environment
91 // and instead uses the PATH of the spawning process.
92 subprocess_option_search_user_path = 0x10
93};
94
95#if defined(__cplusplus)
96extern "C" {
97#endif
98
99/// @brief Create a process.
100/// @param command_line An array of strings for the command line to execute for
101/// this process. The last element must be NULL to signify the end of the array.
102/// The memory backing this parameter only needs to persist until this function
103/// returns.
104/// @param options A bit field of subprocess_option_e's to pass.
105/// @param out_process The newly created process.
106/// @return On success zero is returned.
107subprocess_weak int subprocess_create(const char *const command_line[],
108 int options,
109 struct subprocess_s *const out_process);
110
111/// @brief Create a process (extended create).
112/// @param command_line An array of strings for the command line to execute for
113/// this process. The last element must be NULL to signify the end of the array.
114/// The memory backing this parameter only needs to persist until this function
115/// returns.
116/// @param options A bit field of subprocess_option_e's to pass.
117/// @param environment An optional array of strings for the environment to use
118/// for a child process (each element of the form FOO=BAR). The last element
119/// must be NULL to signify the end of the array.
120/// @param out_process The newly created process.
121/// @return On success zero is returned.
122///
123/// If `options` contains `subprocess_option_inherit_environment`, then
124/// `environment` must be NULL.
125subprocess_weak int
126subprocess_create_ex(const char *const command_line[], int options,
127 const char *const environment[],
128 struct subprocess_s *const out_process);
129
130/// @brief Get the standard input file for a process.
131/// @param process The process to query.
132/// @return The file for standard input of the process.
133///
134/// The file returned can be written to by the parent process to feed data to
135/// the standard input of the process.
136subprocess_pure subprocess_weak FILE *
137subprocess_stdin(const struct subprocess_s *const process);
138
139/// @brief Get the standard output file for a process.
140/// @param process The process to query.
141/// @return The file for standard output of the process.
142///
143/// The file returned can be read from by the parent process to read data from
144/// the standard output of the child process.
145subprocess_pure subprocess_weak FILE *
146subprocess_stdout(const struct subprocess_s *const process);
147
148/// @brief Get the standard error file for a process.
149/// @param process The process to query.
150/// @return The file for standard error of the process.
151///
152/// The file returned can be read from by the parent process to read data from
153/// the standard error of the child process.
154///
155/// If the process was created with the subprocess_option_combined_stdout_stderr
156/// option bit set, this function will return NULL, and the subprocess_stdout
157/// function should be used for both the standard output and error combined.
158subprocess_pure subprocess_weak FILE *
159subprocess_stderr(const struct subprocess_s *const process);
160
161/// @brief Wait for a process to finish execution.
162/// @param process The process to wait for.
163/// @param out_return_code The return code of the returned process (can be
164/// NULL).
165/// @return On success zero is returned.
166///
167/// Joining a process will close the stdin pipe to the process.
168subprocess_weak int subprocess_join(struct subprocess_s *const process,
169 int *const out_return_code);
170
171/// @brief Destroy a previously created process.
172/// @param process The process to destroy.
173/// @return On success zero is returned.
174///
175/// If the process to be destroyed had not finished execution, it may out live
176/// the parent process.
177subprocess_weak int subprocess_destroy(struct subprocess_s *const process);
178
179/// @brief Terminate a previously created process.
180/// @param process The process to terminate.
181/// @return On success zero is returned.
182///
183/// If the process to be destroyed had not finished execution, it will be
184/// terminated (i.e killed).
185subprocess_weak int subprocess_terminate(struct subprocess_s *const process);
186
187/// @brief Read the standard output from the child process.
188/// @param process The process to read from.
189/// @param buffer The buffer to read into.
190/// @param size The maximum number of bytes to read.
191/// @return The number of bytes actually read into buffer. Can only be 0 if the
192/// process has complete.
193///
194/// The only safe way to read from the standard output of a process during it's
195/// execution is to use the `subprocess_option_enable_async` option in
196/// conjunction with this method.
197subprocess_weak unsigned
198subprocess_read_stdout(struct subprocess_s *const process, char *const buffer,
199 unsigned size);
200
201/// @brief Read the standard error from the child process.
202/// @param process The process to read from.
203/// @param buffer The buffer to read into.
204/// @param size The maximum number of bytes to read.
205/// @return The number of bytes actually read into buffer. Can only be 0 if the
206/// process has complete.
207///
208/// The only safe way to read from the standard error of a process during it's
209/// execution is to use the `subprocess_option_enable_async` option in
210/// conjunction with this method.
211subprocess_weak unsigned
212subprocess_read_stderr(struct subprocess_s *const process, char *const buffer,
213 unsigned size);
214
215/// @brief Returns if the subprocess is currently still alive and executing.
216/// @param process The process to check.
217/// @return If the process is still alive non-zero is returned.
218subprocess_weak int subprocess_alive(struct subprocess_s *const process);
219
220#if defined(__cplusplus)
221#define SUBPROCESS_CAST(type, x) static_cast<type>(x)
222#define SUBPROCESS_PTR_CAST(type, x) reinterpret_cast<type>(x)
223#define SUBPROCESS_CONST_CAST(type, x) const_cast<type>(x)
224#define SUBPROCESS_NULL NULL
225#else
226#define SUBPROCESS_CAST(type, x) ((type)(x))
227#define SUBPROCESS_PTR_CAST(type, x) ((type)(x))
228#define SUBPROCESS_CONST_CAST(type, x) ((type)(x))
229#define SUBPROCESS_NULL 0
230#endif
231
232#if !defined(_WIN32)
233#include <signal.h>
234#include <spawn.h>
235#include <stdlib.h>
236#include <sys/types.h>
237#include <sys/wait.h>
238#include <unistd.h>
239#endif
240
241#if defined(_WIN32)
242
243#if (_MSC_VER < 1920)
244#ifdef _WIN64
245typedef __int64 subprocess_intptr_t;
246typedef unsigned __int64 subprocess_size_t;
247#else
248typedef int subprocess_intptr_t;
249typedef unsigned int subprocess_size_t;
250#endif
251#else
252#include <inttypes.h>
253
254typedef intptr_t subprocess_intptr_t;
255typedef size_t subprocess_size_t;
256#endif
257
258#ifdef __clang__
259#pragma clang diagnostic push
260#pragma clang diagnostic ignored "-Wreserved-identifier"
261#endif
262
263typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION;
264typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES;
265typedef struct _STARTUPINFOA *LPSTARTUPINFOA;
266typedef struct _OVERLAPPED *LPOVERLAPPED;
267
268#ifdef __clang__
269#pragma clang diagnostic pop
270#endif
271
272#ifdef _MSC_VER
273#pragma warning(push, 1)
274#endif
275#ifdef __MINGW32__
276#pragma GCC diagnostic push
277#pragma GCC diagnostic ignored "-Wpedantic"
278#endif
279
280struct subprocess_subprocess_information_s {
281 void *hProcess;
282 void *hThread;
283 unsigned long dwProcessId;
284 unsigned long dwThreadId;
285};
286
287struct subprocess_security_attributes_s {
288 unsigned long nLength;
289 void *lpSecurityDescriptor;
290 int bInheritHandle;
291};
292
293struct subprocess_startup_info_s {
294 unsigned long cb;
295 char *lpReserved;
296 char *lpDesktop;
297 char *lpTitle;
298 unsigned long dwX;
299 unsigned long dwY;
300 unsigned long dwXSize;
301 unsigned long dwYSize;
302 unsigned long dwXCountChars;
303 unsigned long dwYCountChars;
304 unsigned long dwFillAttribute;
305 unsigned long dwFlags;
306 unsigned short wShowWindow;
307 unsigned short cbReserved2;
308 unsigned char *lpReserved2;
309 void *hStdInput;
310 void *hStdOutput;
311 void *hStdError;
312};
313
314struct subprocess_overlapped_s {
315 uintptr_t Internal;
316 uintptr_t InternalHigh;
317 union {
318 struct {
319 unsigned long Offset;
320 unsigned long OffsetHigh;
321 } DUMMYSTRUCTNAME;
322 void *Pointer;
323 } DUMMYUNIONNAME;
324
325 void *hEvent;
326};
327
328#ifdef __MINGW32__
329#pragma GCC diagnostic pop
330#endif
331#ifdef _MSC_VER
332#pragma warning(pop)
333#endif
334
335__declspec(dllimport) unsigned long __stdcall GetLastError(void);
336__declspec(dllimport) int __stdcall SetHandleInformation(void *, unsigned long,
337 unsigned long);
338__declspec(dllimport) int __stdcall CreatePipe(void **, void **,
339 LPSECURITY_ATTRIBUTES,
340 unsigned long);
341__declspec(dllimport) void *__stdcall CreateNamedPipeA(
342 const char *, unsigned long, unsigned long, unsigned long, unsigned long,
343 unsigned long, unsigned long, LPSECURITY_ATTRIBUTES);
344__declspec(dllimport) int __stdcall ReadFile(void *, void *, unsigned long,
345 unsigned long *, LPOVERLAPPED);
346__declspec(dllimport) unsigned long __stdcall GetCurrentProcessId(void);
347__declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void);
348__declspec(dllimport) void *__stdcall CreateFileA(const char *, unsigned long,
349 unsigned long,
350 LPSECURITY_ATTRIBUTES,
351 unsigned long, unsigned long,
352 void *);
353__declspec(dllimport) void *__stdcall CreateEventA(LPSECURITY_ATTRIBUTES, int,
354 int, const char *);
355__declspec(dllimport) int __stdcall CreateProcessA(
356 const char *, char *, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, int,
357 unsigned long, void *, const char *, LPSTARTUPINFOA, LPPROCESS_INFORMATION);
358__declspec(dllimport) int __stdcall CloseHandle(void *);
359__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(
360 void *, unsigned long);
361__declspec(dllimport) int __stdcall GetExitCodeProcess(
362 void *, unsigned long *lpExitCode);
363__declspec(dllimport) int __stdcall TerminateProcess(void *, unsigned int);
364__declspec(dllimport) unsigned long __stdcall WaitForMultipleObjects(
365 unsigned long, void *const *, int, unsigned long);
366__declspec(dllimport) int __stdcall GetOverlappedResult(void *, LPOVERLAPPED,
367 unsigned long *, int);
368
369#if defined(_DLL)
370#define SUBPROCESS_DLLIMPORT __declspec(dllimport)
371#else
372#define SUBPROCESS_DLLIMPORT
373#endif
374
375#ifdef __clang__
376#pragma clang diagnostic push
377#pragma clang diagnostic ignored "-Wreserved-identifier"
378#endif
379
380SUBPROCESS_DLLIMPORT int __cdecl _fileno(FILE *);
381SUBPROCESS_DLLIMPORT int __cdecl _open_osfhandle(subprocess_intptr_t, int);
382SUBPROCESS_DLLIMPORT subprocess_intptr_t __cdecl _get_osfhandle(int);
383
384#ifndef __MINGW32__
385void *__cdecl _alloca(subprocess_size_t);
386#else
387#include <malloc.h>
388#endif
389
390#ifdef __clang__
391#pragma clang diagnostic pop
392#endif
393
394#else
395typedef size_t subprocess_size_t;
396#endif
397
398#ifdef __clang__
399#pragma clang diagnostic push
400#pragma clang diagnostic ignored "-Wpadded"
401#endif
402struct subprocess_s {
403 FILE *stdin_file;
404 FILE *stdout_file;
405 FILE *stderr_file;
406
407#if defined(_WIN32)
408 void *hProcess;
409 void *hStdInput;
410 void *hEventOutput;
411 void *hEventError;
412#else
413 pid_t child;
414 int return_status;
415#endif
416
417 subprocess_size_t alive;
418};
419#ifdef __clang__
420#pragma clang diagnostic pop
421#endif
422
423#if defined(__clang__)
424#if __has_warning("-Wunsafe-buffer-usage")
425#pragma clang diagnostic push
426#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
427#endif
428#endif
429
430#if defined(_WIN32)
431subprocess_weak int subprocess_create_named_pipe_helper(void **rd, void **wr);
432int subprocess_create_named_pipe_helper(void **rd, void **wr) {
433 const unsigned long pipeAccessInbound = 0x00000001;
434 const unsigned long fileFlagOverlapped = 0x40000000;
435 const unsigned long pipeTypeByte = 0x00000000;
436 const unsigned long pipeWait = 0x00000000;
437 const unsigned long genericWrite = 0x40000000;
438 const unsigned long openExisting = 3;
439 const unsigned long fileAttributeNormal = 0x00000080;
440 const void *const invalidHandleValue =
441 SUBPROCESS_PTR_CAST(void *, ~(SUBPROCESS_CAST(subprocess_intptr_t, 0)));
442 struct subprocess_security_attributes_s saAttr = {sizeof(saAttr),
443 SUBPROCESS_NULL, 1};
444 char name[256] = {0};
445 static subprocess_tls long index = 0;
446 const long unique = index++;
447
448#if defined(_MSC_VER) && _MSC_VER < 1900
449#pragma warning(push, 1)
450#pragma warning(disable : 4996)
451 _snprintf(name, sizeof(name) - 1,
452 "\\\\.\\pipe\\sheredom_subprocess_h.%08lx.%08lx.%ld",
453 GetCurrentProcessId(), GetCurrentThreadId(), unique);
454#pragma warning(pop)
455#else
456 snprintf(name, sizeof(name) - 1,
457 "\\\\.\\pipe\\sheredom_subprocess_h.%08lx.%08lx.%ld",
458 GetCurrentProcessId(), GetCurrentThreadId(), unique);
459#endif
460
461 *rd =
462 CreateNamedPipeA(name, pipeAccessInbound | fileFlagOverlapped,
463 pipeTypeByte | pipeWait, 1, 4096, 4096, SUBPROCESS_NULL,
464 SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr));
465
466 if (invalidHandleValue == *rd) {
467 return -1;
468 }
469
470 *wr = CreateFileA(name, genericWrite, SUBPROCESS_NULL,
471 SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr),
472 openExisting, fileAttributeNormal, SUBPROCESS_NULL);
473
474 if (invalidHandleValue == *wr) {
475 return -1;
476 }
477
478 return 0;
479}
480#endif
481
482int subprocess_create(const char *const commandLine[], int options,
483 struct subprocess_s *const out_process) {
484 return subprocess_create_ex(commandLine, options, SUBPROCESS_NULL,
485 out_process);
486}
487
488int subprocess_create_ex(const char *const commandLine[], int options,
489 const char *const environment[],
490 struct subprocess_s *const out_process) {
491#if defined(_WIN32)
492 int fd;
493 void *rd, *wr;
494 char *commandLineCombined;
495 subprocess_size_t len;
496 int i, j;
497 int need_quoting;
498 unsigned long flags = 0;
499 const unsigned long startFUseStdHandles = 0x00000100;
500 const unsigned long handleFlagInherit = 0x00000001;
501 const unsigned long createNoWindow = 0x08000000;
502 struct subprocess_subprocess_information_s processInfo;
503 struct subprocess_security_attributes_s saAttr = {sizeof(saAttr),
504 SUBPROCESS_NULL, 1};
505 char *used_environment = SUBPROCESS_NULL;
506 struct subprocess_startup_info_s startInfo = {0,
507 SUBPROCESS_NULL,
508 SUBPROCESS_NULL,
509 SUBPROCESS_NULL,
510 0,
511 0,
512 0,
513 0,
514 0,
515 0,
516 0,
517 0,
518 0,
519 0,
520 SUBPROCESS_NULL,
521 SUBPROCESS_NULL,
522 SUBPROCESS_NULL,
523 SUBPROCESS_NULL};
524
525 startInfo.cb = sizeof(startInfo);
526 startInfo.dwFlags = startFUseStdHandles;
527
528 if (subprocess_option_no_window == (options & subprocess_option_no_window)) {
529 flags |= createNoWindow;
530 }
531
532 if (subprocess_option_inherit_environment !=
533 (options & subprocess_option_inherit_environment)) {
534 if (SUBPROCESS_NULL == environment) {
535 used_environment = SUBPROCESS_CONST_CAST(char *, "\0\0");
536 } else {
537 // We always end with two null terminators.
538 len = 2;
539
540 for (i = 0; environment[i]; i++) {
541 for (j = 0; '\0' != environment[i][j]; j++) {
542 len++;
543 }
544
545 // For the null terminator too.
546 len++;
547 }
548
549 used_environment = SUBPROCESS_CAST(char *, _alloca(len));
550
551 // Re-use len for the insertion position
552 len = 0;
553
554 for (i = 0; environment[i]; i++) {
555 for (j = 0; '\0' != environment[i][j]; j++) {
556 used_environment[len++] = environment[i][j];
557 }
558
559 used_environment[len++] = '\0';
560 }
561
562 // End with the two null terminators.
563 used_environment[len++] = '\0';
564 used_environment[len++] = '\0';
565 }
566 } else {
567 if (SUBPROCESS_NULL != environment) {
568 return -1;
569 }
570 }
571
572 if (!CreatePipe(&rd, &wr, SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr),
573 0)) {
574 return -1;
575 }
576
577 if (!SetHandleInformation(wr, handleFlagInherit, 0)) {
578 return -1;
579 }
580
581 fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, wr), 0);
582
583 if (-1 != fd) {
584 out_process->stdin_file = _fdopen(fd, "wb");
585
586 if (SUBPROCESS_NULL == out_process->stdin_file) {
587 return -1;
588 }
589 }
590
591 startInfo.hStdInput = rd;
592
593 if (options & subprocess_option_enable_async) {
594 if (subprocess_create_named_pipe_helper(&rd, &wr)) {
595 return -1;
596 }
597 } else {
598 if (!CreatePipe(&rd, &wr,
599 SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) {
600 return -1;
601 }
602 }
603
604 if (!SetHandleInformation(rd, handleFlagInherit, 0)) {
605 return -1;
606 }
607
608 fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0);
609
610 if (-1 != fd) {
611 out_process->stdout_file = _fdopen(fd, "rb");
612
613 if (SUBPROCESS_NULL == out_process->stdout_file) {
614 return -1;
615 }
616 }
617
618 startInfo.hStdOutput = wr;
619
620 if (subprocess_option_combined_stdout_stderr ==
621 (options & subprocess_option_combined_stdout_stderr)) {
622 out_process->stderr_file = out_process->stdout_file;
623 startInfo.hStdError = startInfo.hStdOutput;
624 } else {
625 if (options & subprocess_option_enable_async) {
626 if (subprocess_create_named_pipe_helper(&rd, &wr)) {
627 return -1;
628 }
629 } else {
630 if (!CreatePipe(&rd, &wr,
631 SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) {
632 return -1;
633 }
634 }
635
636 if (!SetHandleInformation(rd, handleFlagInherit, 0)) {
637 return -1;
638 }
639
640 fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0);
641
642 if (-1 != fd) {
643 out_process->stderr_file = _fdopen(fd, "rb");
644
645 if (SUBPROCESS_NULL == out_process->stderr_file) {
646 return -1;
647 }
648 }
649
650 startInfo.hStdError = wr;
651 }
652
653 if (options & subprocess_option_enable_async) {
654 out_process->hEventOutput =
655 CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1,
656 SUBPROCESS_NULL);
657 out_process->hEventError =
658 CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1,
659 SUBPROCESS_NULL);
660 } else {
661 out_process->hEventOutput = SUBPROCESS_NULL;
662 out_process->hEventError = SUBPROCESS_NULL;
663 }
664
665 // Combine commandLine together into a single string
666 len = 0;
667 for (i = 0; commandLine[i]; i++) {
668 // for the trailing \0
669 len++;
670
671 // Quote the argument if it has a space in it
672 if (strpbrk(commandLine[i], "\t\v ") != SUBPROCESS_NULL ||
673 commandLine[i][0] == SUBPROCESS_NULL)
674 len += 2;
675
676 for (j = 0; '\0' != commandLine[i][j]; j++) {
677 switch (commandLine[i][j]) {
678 default:
679 break;
680 case '\\':
681 if (commandLine[i][j + 1] == '"') {
682 len++;
683 }
684
685 break;
686 case '"':
687 len++;
688 break;
689 }
690 len++;
691 }
692 }
693
694 commandLineCombined = SUBPROCESS_CAST(char *, _alloca(len));
695
696 if (!commandLineCombined) {
697 return -1;
698 }
699
700 // Gonna re-use len to store the write index into commandLineCombined
701 len = 0;
702
703 for (i = 0; commandLine[i]; i++) {
704 if (0 != i) {
705 commandLineCombined[len++] = ' ';
706 }
707
708 need_quoting = strpbrk(commandLine[i], "\t\v ") != SUBPROCESS_NULL ||
709 commandLine[i][0] == SUBPROCESS_NULL;
710 if (need_quoting) {
711 commandLineCombined[len++] = '"';
712 }
713
714 for (j = 0; '\0' != commandLine[i][j]; j++) {
715 switch (commandLine[i][j]) {
716 default:
717 break;
718 case '\\':
719 if (commandLine[i][j + 1] == '"') {
720 commandLineCombined[len++] = '\\';
721 }
722
723 break;
724 case '"':
725 commandLineCombined[len++] = '\\';
726 break;
727 }
728
729 commandLineCombined[len++] = commandLine[i][j];
730 }
731 if (need_quoting) {
732 commandLineCombined[len++] = '"';
733 }
734 }
735
736 commandLineCombined[len] = '\0';
737
738 if (!CreateProcessA(
739 SUBPROCESS_NULL,
740 commandLineCombined, // command line
741 SUBPROCESS_NULL, // process security attributes
742 SUBPROCESS_NULL, // primary thread security attributes
743 1, // handles are inherited
744 flags, // creation flags
745 used_environment, // used environment
746 SUBPROCESS_NULL, // use parent's current directory
747 SUBPROCESS_PTR_CAST(LPSTARTUPINFOA,
748 &startInfo), // STARTUPINFO pointer
749 SUBPROCESS_PTR_CAST(LPPROCESS_INFORMATION, &processInfo))) {
750 return -1;
751 }
752
753 out_process->hProcess = processInfo.hProcess;
754
755 out_process->hStdInput = startInfo.hStdInput;
756
757 // We don't need the handle of the primary thread in the called process.
758 CloseHandle(processInfo.hThread);
759
760 if (SUBPROCESS_NULL != startInfo.hStdOutput) {
761 CloseHandle(startInfo.hStdOutput);
762
763 if (startInfo.hStdError != startInfo.hStdOutput) {
764 CloseHandle(startInfo.hStdError);
765 }
766 }
767
768 out_process->alive = 1;
769
770 return 0;
771#else
772 int stdinfd[2];
773 int stdoutfd[2];
774 int stderrfd[2];
775 pid_t child;
776 extern char **environ;
777 char *const empty_environment[1] = {SUBPROCESS_NULL};
778 posix_spawn_file_actions_t actions;
779 char *const *used_environment;
780
781 if (subprocess_option_inherit_environment ==
782 (options & subprocess_option_inherit_environment)) {
783 if (SUBPROCESS_NULL != environment) {
784 return -1;
785 }
786 }
787
788 if (0 != pipe(stdinfd)) {
789 return -1;
790 }
791
792 if (0 != pipe(stdoutfd)) {
793 return -1;
794 }
795
796 if (subprocess_option_combined_stdout_stderr !=
797 (options & subprocess_option_combined_stdout_stderr)) {
798 if (0 != pipe(stderrfd)) {
799 return -1;
800 }
801 }
802
803 if (environment) {
804#ifdef __clang__
805#pragma clang diagnostic push
806#pragma clang diagnostic ignored "-Wcast-qual"
807#pragma clang diagnostic ignored "-Wold-style-cast"
808#endif
809 used_environment = SUBPROCESS_CONST_CAST(char *const *, environment);
810#ifdef __clang__
811#pragma clang diagnostic pop
812#endif
813 } else if (subprocess_option_inherit_environment ==
814 (options & subprocess_option_inherit_environment)) {
815 used_environment = environ;
816 } else {
817 used_environment = empty_environment;
818 }
819
820 if (0 != posix_spawn_file_actions_init(&actions)) {
821 return -1;
822 }
823
824 // Close the stdin write end
825 if (0 != posix_spawn_file_actions_addclose(&actions, stdinfd[1])) {
826 posix_spawn_file_actions_destroy(&actions);
827 return -1;
828 }
829
830 // Map the read end to stdin
831 if (0 !=
832 posix_spawn_file_actions_adddup2(&actions, stdinfd[0], STDIN_FILENO)) {
833 posix_spawn_file_actions_destroy(&actions);
834 return -1;
835 }
836
837 // Close the stdout read end
838 if (0 != posix_spawn_file_actions_addclose(&actions, stdoutfd[0])) {
839 posix_spawn_file_actions_destroy(&actions);
840 return -1;
841 }
842
843 // Map the write end to stdout
844 if (0 !=
845 posix_spawn_file_actions_adddup2(&actions, stdoutfd[1], STDOUT_FILENO)) {
846 posix_spawn_file_actions_destroy(&actions);
847 return -1;
848 }
849
850 if (subprocess_option_combined_stdout_stderr ==
851 (options & subprocess_option_combined_stdout_stderr)) {
852 if (0 != posix_spawn_file_actions_adddup2(&actions, STDOUT_FILENO,
853 STDERR_FILENO)) {
854 posix_spawn_file_actions_destroy(&actions);
855 return -1;
856 }
857 } else {
858 // Close the stderr read end
859 if (0 != posix_spawn_file_actions_addclose(&actions, stderrfd[0])) {
860 posix_spawn_file_actions_destroy(&actions);
861 return -1;
862 }
863 // Map the write end to stdout
864 if (0 != posix_spawn_file_actions_adddup2(&actions, stderrfd[1],
865 STDERR_FILENO)) {
866 posix_spawn_file_actions_destroy(&actions);
867 return -1;
868 }
869 }
870
871#ifdef __clang__
872#pragma clang diagnostic push
873#pragma clang diagnostic ignored "-Wcast-qual"
874#pragma clang diagnostic ignored "-Wold-style-cast"
875#endif
876 if (subprocess_option_search_user_path ==
877 (options & subprocess_option_search_user_path)) {
878 if (0 != posix_spawnp(&child, commandLine[0], &actions, SUBPROCESS_NULL,
879 SUBPROCESS_CONST_CAST(char *const *, commandLine),
880 used_environment)) {
881 posix_spawn_file_actions_destroy(&actions);
882 return -1;
883 }
884 } else {
885 if (0 != posix_spawn(&child, commandLine[0], &actions, SUBPROCESS_NULL,
886 SUBPROCESS_CONST_CAST(char *const *, commandLine),
887 used_environment)) {
888 posix_spawn_file_actions_destroy(&actions);
889 return -1;
890 }
891 }
892#ifdef __clang__
893#pragma clang diagnostic pop
894#endif
895
896 // Close the stdin read end
897 close(stdinfd[0]);
898 // Store the stdin write end
899 out_process->stdin_file = fdopen(stdinfd[1], "wb");
900
901 // Close the stdout write end
902 close(stdoutfd[1]);
903 // Store the stdout read end
904 out_process->stdout_file = fdopen(stdoutfd[0], "rb");
905
906 if (subprocess_option_combined_stdout_stderr ==
907 (options & subprocess_option_combined_stdout_stderr)) {
908 out_process->stderr_file = out_process->stdout_file;
909 } else {
910 // Close the stderr write end
911 close(stderrfd[1]);
912 // Store the stderr read end
913 out_process->stderr_file = fdopen(stderrfd[0], "rb");
914 }
915
916 // Store the child's pid
917 out_process->child = child;
918
919 out_process->alive = 1;
920
921 posix_spawn_file_actions_destroy(&actions);
922 return 0;
923#endif
924}
925
926FILE *subprocess_stdin(const struct subprocess_s *const process) {
927 return process->stdin_file;
928}
929
930FILE *subprocess_stdout(const struct subprocess_s *const process) {
931 return process->stdout_file;
932}
933
934FILE *subprocess_stderr(const struct subprocess_s *const process) {
935 if (process->stdout_file != process->stderr_file) {
936 return process->stderr_file;
937 } else {
938 return SUBPROCESS_NULL;
939 }
940}
941
942int subprocess_join(struct subprocess_s *const process,
943 int *const out_return_code) {
944#if defined(_WIN32)
945 const unsigned long infinite = 0xFFFFFFFF;
946
947 if (process->stdin_file) {
948 fclose(process->stdin_file);
949 process->stdin_file = SUBPROCESS_NULL;
950 }
951
952 if (process->hStdInput) {
953 CloseHandle(process->hStdInput);
954 process->hStdInput = SUBPROCESS_NULL;
955 }
956
957 WaitForSingleObject(process->hProcess, infinite);
958
959 if (out_return_code) {
960 if (!GetExitCodeProcess(
961 process->hProcess,
962 SUBPROCESS_PTR_CAST(unsigned long *, out_return_code))) {
963 return -1;
964 }
965 }
966
967 process->alive = 0;
968
969 return 0;
970#else
971 int status;
972
973 if (process->stdin_file) {
974 fclose(process->stdin_file);
975 process->stdin_file = SUBPROCESS_NULL;
976 }
977
978 if (process->child) {
979 if (process->child != waitpid(process->child, &status, 0)) {
980 return -1;
981 }
982
983 process->child = 0;
984
985 if (WIFEXITED(status)) {
986 process->return_status = WEXITSTATUS(status);
987 } else {
988 process->return_status = EXIT_FAILURE;
989 }
990
991 process->alive = 0;
992 }
993
994 if (out_return_code) {
995 *out_return_code = process->return_status;
996 }
997
998 return 0;
999#endif
1000}
1001
1002int subprocess_destroy(struct subprocess_s *const process) {
1003 if (process->stdin_file) {
1004 fclose(process->stdin_file);
1005 process->stdin_file = SUBPROCESS_NULL;
1006 }
1007
1008 if (process->stdout_file) {
1009 fclose(process->stdout_file);
1010
1011 if (process->stdout_file != process->stderr_file) {
1012 fclose(process->stderr_file);
1013 }
1014
1015 process->stdout_file = SUBPROCESS_NULL;
1016 process->stderr_file = SUBPROCESS_NULL;
1017 }
1018
1019#if defined(_WIN32)
1020 if (process->hProcess) {
1021 CloseHandle(process->hProcess);
1022 process->hProcess = SUBPROCESS_NULL;
1023
1024 if (process->hStdInput) {
1025 CloseHandle(process->hStdInput);
1026 }
1027
1028 if (process->hEventOutput) {
1029 CloseHandle(process->hEventOutput);
1030 }
1031
1032 if (process->hEventError) {
1033 CloseHandle(process->hEventError);
1034 }
1035 }
1036#endif
1037
1038 return 0;
1039}
1040
1041int subprocess_terminate(struct subprocess_s *const process) {
1042#if defined(_WIN32)
1043 unsigned int killed_process_exit_code;
1044 int success_terminate;
1045 int windows_call_result;
1046
1047 killed_process_exit_code = 99;
1048 windows_call_result =
1049 TerminateProcess(process->hProcess, killed_process_exit_code);
1050 success_terminate = (windows_call_result == 0) ? 1 : 0;
1051 return success_terminate;
1052#else
1053 int result;
1054 result = kill(process->child, 9);
1055 return result;
1056#endif
1057}
1058
1059unsigned subprocess_read_stdout(struct subprocess_s *const process,
1060 char *const buffer, unsigned size) {
1061#if defined(_WIN32)
1062 void *handle;
1063 unsigned long bytes_read = 0;
1064 struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL};
1065 overlapped.hEvent = process->hEventOutput;
1066
1067 handle = SUBPROCESS_PTR_CAST(void *,
1068 _get_osfhandle(_fileno(process->stdout_file)));
1069
1070 if (!ReadFile(handle, buffer, size, &bytes_read,
1071 SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) {
1072 const unsigned long errorIoPending = 997;
1073 unsigned long error = GetLastError();
1074
1075 // Means we've got an async read!
1076 if (error == errorIoPending) {
1077 if (!GetOverlappedResult(handle,
1078 SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped),
1079 &bytes_read, 1)) {
1080 const unsigned long errorIoIncomplete = 996;
1081 const unsigned long errorHandleEOF = 38;
1082 error = GetLastError();
1083
1084 if ((error != errorIoIncomplete) && (error != errorHandleEOF)) {
1085 return 0;
1086 }
1087 }
1088 }
1089 }
1090
1091 return SUBPROCESS_CAST(unsigned, bytes_read);
1092#else
1093 const int fd = fileno(process->stdout_file);
1094 const ssize_t bytes_read = read(fd, buffer, size);
1095
1096 if (bytes_read < 0) {
1097 return 0;
1098 }
1099
1100 return SUBPROCESS_CAST(unsigned, bytes_read);
1101#endif
1102}
1103
1104unsigned subprocess_read_stderr(struct subprocess_s *const process,
1105 char *const buffer, unsigned size) {
1106#if defined(_WIN32)
1107 void *handle;
1108 unsigned long bytes_read = 0;
1109 struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL};
1110 overlapped.hEvent = process->hEventError;
1111
1112 handle = SUBPROCESS_PTR_CAST(void *,
1113 _get_osfhandle(_fileno(process->stderr_file)));
1114
1115 if (!ReadFile(handle, buffer, size, &bytes_read,
1116 SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) {
1117 const unsigned long errorIoPending = 997;
1118 unsigned long error = GetLastError();
1119
1120 // Means we've got an async read!
1121 if (error == errorIoPending) {
1122 if (!GetOverlappedResult(handle,
1123 SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped),
1124 &bytes_read, 1)) {
1125 const unsigned long errorIoIncomplete = 996;
1126 const unsigned long errorHandleEOF = 38;
1127 error = GetLastError();
1128
1129 if ((error != errorIoIncomplete) && (error != errorHandleEOF)) {
1130 return 0;
1131 }
1132 }
1133 }
1134 }
1135
1136 return SUBPROCESS_CAST(unsigned, bytes_read);
1137#else
1138 const int fd = fileno(process->stderr_file);
1139 const ssize_t bytes_read = read(fd, buffer, size);
1140
1141 if (bytes_read < 0) {
1142 return 0;
1143 }
1144
1145 return SUBPROCESS_CAST(unsigned, bytes_read);
1146#endif
1147}
1148
1149int subprocess_alive(struct subprocess_s *const process) {
1150 int is_alive = SUBPROCESS_CAST(int, process->alive);
1151
1152 if (!is_alive) {
1153 return 0;
1154 }
1155#if defined(_WIN32)
1156 {
1157 const unsigned long zero = 0x0;
1158 const unsigned long wait_object_0 = 0x00000000L;
1159
1160 is_alive = wait_object_0 != WaitForSingleObject(process->hProcess, zero);
1161 }
1162#else
1163 {
1164 int status;
1165 is_alive = 0 == waitpid(process->child, &status, WNOHANG);
1166
1167 // If the process was successfully waited on we need to cleanup now.
1168 if (!is_alive) {
1169 if (WIFEXITED(status)) {
1170 process->return_status = WEXITSTATUS(status);
1171 } else {
1172 process->return_status = EXIT_FAILURE;
1173 }
1174
1175 // Since we've already successfully waited on the process, we need to wipe
1176 // the child now.
1177 process->child = 0;
1178
1179 if (subprocess_join(process, SUBPROCESS_NULL)) {
1180 return -1;
1181 }
1182 }
1183 }
1184#endif
1185
1186 if (!is_alive) {
1187 process->alive = 0;
1188 }
1189
1190 return is_alive;
1191}
1192
1193#if defined(__clang__)
1194#if __has_warning("-Wunsafe-buffer-usage")
1195#pragma clang diagnostic pop
1196#endif
1197#endif
1198
1199#if defined(__cplusplus)
1200} // extern "C"
1201#endif
1202
1203#endif /* SHEREDOM_SUBPROCESS_H_INCLUDED */