#ifndef SHEREDOM_SUBPROCESS_H_INCLUDED
#define SHEREDOM_SUBPROCESS_H_INCLUDED
#if defined(_MSC_VER)
#pragma warning(push, 1)
#pragma warning(disable : 4668)
#endif
#include <stdio.h>
#include <string.h>
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
#if defined(__TINYC__)
#define SUBPROCESS_ATTRIBUTE(a) __attribute((a))
#else
#define SUBPROCESS_ATTRIBUTE(a) __attribute__((a))
#endif
#if defined(_MSC_VER)
#define subprocess_pure
#define subprocess_weak __inline
#define subprocess_tls __declspec(thread)
#elif defined(__MINGW32__)
#define subprocess_pure SUBPROCESS_ATTRIBUTE(pure)
#define subprocess_weak static SUBPROCESS_ATTRIBUTE(used)
#define subprocess_tls __thread
#elif defined(__clang__) || defined(__GNUC__) || defined(__TINYC__)
#define subprocess_pure SUBPROCESS_ATTRIBUTE(pure)
#define subprocess_weak SUBPROCESS_ATTRIBUTE(weak)
#define subprocess_tls __thread
#else
#error Non clang, non gcc, non MSVC compiler found!
#endif
struct subprocess_s;
enum subprocess_option_e {
subprocess_option_combined_stdout_stderr = 0x1,
subprocess_option_inherit_environment = 0x2,
subprocess_option_enable_async = 0x4,
subprocess_option_no_window = 0x8,
subprocess_option_search_user_path = 0x10,
subprocess_option_enable_async_no_wait = 0x20
};
enum subprocess_error_e {
subprocess_error_success = 0,
subprocess_error_unknown = -1,
subprocess_error_invalid_options = -2,
subprocess_error_invalid_environment = -3,
subprocess_error_not_found = -4,
subprocess_error_permission_denied = -5,
subprocess_error_no_memory = -6,
subprocess_error_pipe = -7,
subprocess_error_spawn = -8
};
#if defined(__cplusplus)
extern "C" {
#endif
subprocess_weak int subprocess_create(const char *const command_line[],
int options,
struct subprocess_s *const out_process);
subprocess_weak int
subprocess_create_ex(const char *const command_line[], int options,
const char *const environment[],
const char *const process_cwd,
struct subprocess_s *const out_process);
subprocess_pure subprocess_weak FILE *
subprocess_stdin(const struct subprocess_s *const process);
subprocess_pure subprocess_weak FILE *
subprocess_stdout(const struct subprocess_s *const process);
subprocess_pure subprocess_weak FILE *
subprocess_stderr(const struct subprocess_s *const process);
subprocess_weak int subprocess_join(struct subprocess_s *const process,
int *const out_return_code);
subprocess_weak int subprocess_destroy(struct subprocess_s *const process);
subprocess_weak int subprocess_terminate(struct subprocess_s *const process);
subprocess_weak unsigned
subprocess_read_stdout(struct subprocess_s *const process, char *const buffer,
unsigned size);
subprocess_weak unsigned
subprocess_read_stderr(struct subprocess_s *const process, char *const buffer,
unsigned size);
subprocess_weak int subprocess_alive(struct subprocess_s *const process);
#if defined(__cplusplus)
#define SUBPROCESS_CAST(type, x) static_cast<type>(x)
#define SUBPROCESS_PTR_CAST(type, x) reinterpret_cast<type>(x)
#define SUBPROCESS_CONST_CAST(type, x) const_cast<type>(x)
#define SUBPROCESS_NULL NULL
#else
#define SUBPROCESS_CAST(type, x) ((type)(x))
#define SUBPROCESS_PTR_CAST(type, x) ((type)(x))
#define SUBPROCESS_CONST_CAST(type, x) ((type)(x))
#define SUBPROCESS_NULL 0
#endif
#if !defined(_WIN32)
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <spawn.h>
#include <stdlib.h>
#if defined(__APPLE__)
#include <AvailabilityMacros.h>
#endif
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#endif
#if defined(_WIN32)
#include <wchar.h>
#if defined(__clang__)
#if __has_warning("-Wc++-keyword")
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc++-keyword"
#endif
#endif
typedef wchar_t subprocess_wchar_t;
#if defined(__clang__)
#if __has_warning("-Wc++-keyword")
#pragma clang diagnostic pop
#endif
#endif
#if (_MSC_VER < 1920)
#ifdef _WIN64
typedef __int64 subprocess_intptr_t;
typedef unsigned __int64 subprocess_size_t;
#else
typedef int subprocess_intptr_t;
typedef unsigned int subprocess_size_t;
#endif
#else
#include <inttypes.h>
typedef intptr_t subprocess_intptr_t;
typedef size_t subprocess_size_t;
#endif
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreserved-identifier"
#endif
typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION;
typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES;
typedef struct _STARTUPINFOW *LPSTARTUPINFOW;
typedef struct _OVERLAPPED *LPOVERLAPPED;
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#ifdef _MSC_VER
#pragma warning(push, 1)
#endif
#ifdef __MINGW32__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#endif
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
#endif
struct subprocess_subprocess_information_s {
void *hProcess;
void *hThread;
unsigned long dwProcessId;
unsigned long dwThreadId;
};
struct subprocess_security_attributes_s {
unsigned long nLength;
void *lpSecurityDescriptor;
int bInheritHandle;
};
struct subprocess_startup_info_s {
unsigned long cb;
subprocess_wchar_t *lpReserved;
subprocess_wchar_t *lpDesktop;
subprocess_wchar_t *lpTitle;
unsigned long dwX;
unsigned long dwY;
unsigned long dwXSize;
unsigned long dwYSize;
unsigned long dwXCountChars;
unsigned long dwYCountChars;
unsigned long dwFillAttribute;
unsigned long dwFlags;
unsigned short wShowWindow;
unsigned short cbReserved2;
unsigned char *lpReserved2;
void *hStdInput;
void *hStdOutput;
void *hStdError;
};
struct subprocess_overlapped_s {
uintptr_t Internal;
uintptr_t InternalHigh;
union {
struct {
unsigned long Offset;
unsigned long OffsetHigh;
} DUMMYSTRUCTNAME;
void *Pointer;
} DUMMYUNIONNAME;
void *hEvent;
};
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#ifdef __MINGW32__
#pragma GCC diagnostic pop
#endif
#ifdef _MSC_VER
#pragma warning(pop)
#endif
__declspec(dllimport) unsigned long __stdcall GetLastError(void);
__declspec(dllimport) void __stdcall SetLastError(unsigned long);
__declspec(dllimport) int __stdcall SetHandleInformation(void *, unsigned long,
unsigned long);
__declspec(dllimport) int __stdcall CreatePipe(void **, void **,
LPSECURITY_ATTRIBUTES,
unsigned long);
__declspec(dllimport) void *__stdcall CreateNamedPipeA(
const char *, unsigned long, unsigned long, unsigned long, unsigned long,
unsigned long, unsigned long, LPSECURITY_ATTRIBUTES);
__declspec(dllimport) int __stdcall ReadFile(void *, void *, unsigned long,
unsigned long *, LPOVERLAPPED);
__declspec(dllimport) unsigned long __stdcall GetCurrentProcessId(void);
__declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void);
__declspec(dllimport) void *__stdcall CreateFileA(const char *, unsigned long,
unsigned long,
LPSECURITY_ATTRIBUTES,
unsigned long, unsigned long,
void *);
__declspec(dllimport) void *__stdcall CreateEventA(LPSECURITY_ATTRIBUTES, int,
int, const char *);
__declspec(dllimport) int __stdcall CreateProcessW(
const subprocess_wchar_t *, subprocess_wchar_t *, LPSECURITY_ATTRIBUTES,
LPSECURITY_ATTRIBUTES, int, unsigned long, void *,
const subprocess_wchar_t *, LPSTARTUPINFOW, LPPROCESS_INFORMATION);
__declspec(dllimport) int __stdcall MultiByteToWideChar(
unsigned int, unsigned long, const char *, int, subprocess_wchar_t *, int);
__declspec(dllimport) int __stdcall CloseHandle(void *);
__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(
void *, unsigned long);
__declspec(dllimport) int __stdcall GetExitCodeProcess(
void *, unsigned long *lpExitCode);
__declspec(dllimport) int __stdcall TerminateProcess(void *, unsigned int);
__declspec(dllimport) unsigned long __stdcall WaitForMultipleObjects(
unsigned long, void *const *, int, unsigned long);
__declspec(dllimport) int __stdcall GetOverlappedResult(void *, LPOVERLAPPED,
unsigned long *, int);
__declspec(dllimport) int __stdcall PeekNamedPipe(void *, void *, unsigned long,
unsigned long *,
unsigned long *,
unsigned long *);
#if defined(_DLL)
#define SUBPROCESS_DLLIMPORT __declspec(dllimport)
#else
#define SUBPROCESS_DLLIMPORT
#endif
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreserved-identifier"
#endif
SUBPROCESS_DLLIMPORT int __cdecl _fileno(FILE *);
SUBPROCESS_DLLIMPORT int __cdecl _open_osfhandle(subprocess_intptr_t, int);
SUBPROCESS_DLLIMPORT subprocess_intptr_t __cdecl _get_osfhandle(int);
SUBPROCESS_DLLIMPORT int __cdecl _close(int);
#ifndef __MINGW32__
void *__cdecl _alloca(subprocess_size_t);
#else
#include <malloc.h>
#endif
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#else
typedef size_t subprocess_size_t;
#endif
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
#endif
struct subprocess_s {
FILE *stdin_file;
FILE *stdout_file;
FILE *stderr_file;
#if defined(_WIN32)
void *hProcess;
void *hStdInput;
void *hEventOutput;
void *hEventError;
#else
pid_t child;
int return_status;
#endif
int alive;
int no_wait;
};
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#if defined(_WIN32)
subprocess_weak int subprocess_error_from_windows_error(unsigned long error);
int subprocess_error_from_windows_error(unsigned long error) {
enum {
errorFileNotFound = 2,
errorPathNotFound = 3,
errorTooManyOpenFiles = 4,
errorAccessDenied = 5,
errorInvalidHandle = 6,
errorNotEnoughMemory = 8,
errorOutOfMemory = 14,
errorInvalidDrive = 15,
errorBadPathname = 161,
errorDirectory = 267
};
switch (error) {
case errorFileNotFound:
case errorPathNotFound:
case errorInvalidDrive:
case errorBadPathname:
case errorDirectory:
return subprocess_error_not_found;
case errorAccessDenied:
return subprocess_error_permission_denied;
case errorTooManyOpenFiles:
case errorNotEnoughMemory:
case errorOutOfMemory:
return subprocess_error_no_memory;
case errorInvalidHandle:
return subprocess_error_pipe;
default:
return subprocess_error_unknown;
}
}
#else
subprocess_weak int subprocess_error_from_errno(int error);
int subprocess_error_from_errno(int error) {
switch (error) {
case ENOENT:
case ENOTDIR:
return subprocess_error_not_found;
case EACCES:
case EPERM:
return subprocess_error_permission_denied;
case EMFILE:
case ENFILE:
case ENOMEM:
return subprocess_error_no_memory;
default:
return subprocess_error_unknown;
}
}
#endif
#if defined(__clang__)
#if __has_warning("-Wunsafe-buffer-usage")
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
#endif
#endif
#if defined(_WIN32)
subprocess_weak int subprocess_create_named_pipe_helper(void **rd, void **wr);
subprocess_weak void subprocess_close_handle(void **handle);
void subprocess_close_handle(void **handle) {
const void *const invalidHandleValue =
SUBPROCESS_PTR_CAST(void *, ~(SUBPROCESS_CAST(subprocess_intptr_t, 0)));
if (*handle && (invalidHandleValue != *handle)) {
CloseHandle(*handle);
}
*handle = SUBPROCESS_NULL;
}
int subprocess_create_named_pipe_helper(void **rd, void **wr) {
const unsigned long pipeAccessInbound = 0x00000001;
const unsigned long fileFlagOverlapped = 0x40000000;
const unsigned long pipeTypeByte = 0x00000000;
const unsigned long pipeWait = 0x00000000;
const unsigned long genericWrite = 0x40000000;
const unsigned long openExisting = 3;
const unsigned long fileAttributeNormal = 0x00000080;
const void *const invalidHandleValue =
SUBPROCESS_PTR_CAST(void *, ~(SUBPROCESS_CAST(subprocess_intptr_t, 0)));
struct subprocess_security_attributes_s saAttr = {sizeof(saAttr),
SUBPROCESS_NULL, 1};
char name[256] = {0};
#if defined(__clang__)
#if __has_warning("-Wunique-object-duplication")
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunique-object-duplication"
#endif
#endif
static subprocess_tls long index = 0;
#if defined(__clang__)
#if __has_warning("-Wunique-object-duplication")
#pragma clang diagnostic pop
#endif
#endif
const long unique = index++;
*rd = SUBPROCESS_NULL;
*wr = SUBPROCESS_NULL;
#if defined(_MSC_VER) && _MSC_VER < 1900
#pragma warning(push, 1)
#pragma warning(disable : 4996)
_snprintf(name, sizeof(name) - 1,
"\\\\.\\pipe\\sheredom_subprocess_h.%08lx.%08lx.%ld",
GetCurrentProcessId(), GetCurrentThreadId(), unique);
#pragma warning(pop)
#else
snprintf(name, sizeof(name) - 1,
"\\\\.\\pipe\\sheredom_subprocess_h.%08lx.%08lx.%ld",
GetCurrentProcessId(), GetCurrentThreadId(), unique);
#endif
*rd =
CreateNamedPipeA(name, pipeAccessInbound | fileFlagOverlapped,
pipeTypeByte | pipeWait, 1, 4096, 4096, SUBPROCESS_NULL,
SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr));
if (invalidHandleValue == *rd) {
return -1;
}
*wr = CreateFileA(name, genericWrite, SUBPROCESS_NULL,
SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr),
openExisting, fileAttributeNormal, SUBPROCESS_NULL);
if (invalidHandleValue == *wr) {
subprocess_close_handle(rd);
return -1;
}
return 0;
}
#endif
int subprocess_create(const char *const commandLine[], int options,
struct subprocess_s *const out_process) {
return subprocess_create_ex(commandLine, options, SUBPROCESS_NULL,
SUBPROCESS_NULL, out_process);
}
int subprocess_create_ex(const char *const commandLine[], int options,
const char *const environment[],
const char *const process_cwd,
struct subprocess_s *const out_process) {
#if defined(_WIN32)
int fd;
int async_no_wait;
void *rd = SUBPROCESS_NULL;
void *wr = SUBPROCESS_NULL;
char *commandLineCombined;
subprocess_wchar_t *commandLineCombinedWide = SUBPROCESS_NULL;
subprocess_wchar_t *process_cwd_wide = SUBPROCESS_NULL;
subprocess_size_t len;
int wide_len;
int i, j;
int need_quoting;
unsigned long flags = 0;
unsigned long last_error = 0;
int result = subprocess_error_unknown;
const unsigned int codePageUtf8 = 65001;
const unsigned long mbErrInvalidChars = 0x00000008;
const unsigned long startFUseStdHandles = 0x00000100;
const unsigned long handleFlagInherit = 0x00000001;
const unsigned long createNoWindow = 0x08000000;
const unsigned long createUnicodeEnvironment = 0x00000400;
struct subprocess_subprocess_information_s processInfo = {SUBPROCESS_NULL,
SUBPROCESS_NULL, 0,
0};
struct subprocess_security_attributes_s saAttr = {sizeof(saAttr),
SUBPROCESS_NULL, 1};
subprocess_wchar_t empty_environment[2] = {0, 0};
subprocess_wchar_t *used_environment = SUBPROCESS_NULL;
struct subprocess_startup_info_s startInfo = {0,
SUBPROCESS_NULL,
SUBPROCESS_NULL,
SUBPROCESS_NULL,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
SUBPROCESS_NULL,
SUBPROCESS_NULL,
SUBPROCESS_NULL,
SUBPROCESS_NULL};
async_no_wait = subprocess_option_enable_async_no_wait ==
(options & subprocess_option_enable_async_no_wait);
if (async_no_wait && (subprocess_option_enable_async !=
(options & subprocess_option_enable_async))) {
return subprocess_error_invalid_options;
}
startInfo.cb = sizeof(startInfo);
startInfo.dwFlags = startFUseStdHandles;
if (subprocess_option_no_window == (options & subprocess_option_no_window)) {
flags |= createNoWindow;
}
memset(out_process, 0, sizeof(*out_process));
if (subprocess_option_inherit_environment !=
(options & subprocess_option_inherit_environment)) {
flags |= createUnicodeEnvironment;
if (SUBPROCESS_NULL == environment) {
used_environment = empty_environment;
} else {
len = 1;
for (i = 0; environment[i]; i++) {
wide_len = MultiByteToWideChar(codePageUtf8, mbErrInvalidChars,
environment[i], -1, SUBPROCESS_NULL, 0);
if (0 == wide_len) {
result = subprocess_error_from_windows_error(GetLastError());
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
len += SUBPROCESS_CAST(subprocess_size_t, wide_len);
}
if (((SUBPROCESS_CAST(subprocess_size_t, -1)) /
sizeof(subprocess_wchar_t)) < len) {
result = subprocess_error_no_memory;
goto cleanup;
}
used_environment = SUBPROCESS_CAST(
subprocess_wchar_t *, _alloca(len * sizeof(subprocess_wchar_t)));
if (!used_environment) {
result = subprocess_error_no_memory;
goto cleanup;
}
len = 0;
for (i = 0; environment[i]; i++) {
wide_len = MultiByteToWideChar(codePageUtf8, mbErrInvalidChars,
environment[i], -1, SUBPROCESS_NULL, 0);
if (0 == wide_len) {
result = subprocess_error_from_windows_error(GetLastError());
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
if (0 == MultiByteToWideChar(codePageUtf8, mbErrInvalidChars,
environment[i], -1,
&used_environment[len], wide_len)) {
result = subprocess_error_from_windows_error(GetLastError());
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
len += SUBPROCESS_CAST(subprocess_size_t, wide_len);
}
used_environment[len++] = 0;
}
} else {
if (SUBPROCESS_NULL != environment) {
return subprocess_error_invalid_environment;
}
}
if (!CreatePipe(&rd, &wr, SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr),
0)) {
result = subprocess_error_pipe;
goto cleanup;
}
if (!SetHandleInformation(wr, handleFlagInherit, 0)) {
result = subprocess_error_pipe;
goto cleanup;
}
fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, wr), 0);
if (-1 == fd) {
result = subprocess_error_pipe;
goto cleanup;
}
wr = SUBPROCESS_NULL;
out_process->stdin_file = _fdopen(fd, "wb");
if (SUBPROCESS_NULL == out_process->stdin_file) {
_close(fd);
goto cleanup;
}
startInfo.hStdInput = rd;
rd = SUBPROCESS_NULL;
if (options & subprocess_option_enable_async) {
if (subprocess_create_named_pipe_helper(&rd, &wr)) {
result = subprocess_error_pipe;
goto cleanup;
}
} else {
if (!CreatePipe(&rd, &wr,
SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) {
result = subprocess_error_pipe;
goto cleanup;
}
}
if (!SetHandleInformation(rd, handleFlagInherit, 0)) {
result = subprocess_error_pipe;
goto cleanup;
}
fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0);
if (-1 == fd) {
result = subprocess_error_pipe;
goto cleanup;
}
rd = SUBPROCESS_NULL;
out_process->stdout_file = _fdopen(fd, "rb");
if (SUBPROCESS_NULL == out_process->stdout_file) {
_close(fd);
goto cleanup;
}
startInfo.hStdOutput = wr;
wr = SUBPROCESS_NULL;
if (subprocess_option_combined_stdout_stderr ==
(options & subprocess_option_combined_stdout_stderr)) {
out_process->stderr_file = out_process->stdout_file;
startInfo.hStdError = startInfo.hStdOutput;
} else {
if (options & subprocess_option_enable_async) {
if (subprocess_create_named_pipe_helper(&rd, &wr)) {
result = subprocess_error_pipe;
goto cleanup;
}
} else {
if (!CreatePipe(&rd, &wr,
SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) {
result = subprocess_error_pipe;
goto cleanup;
}
}
if (!SetHandleInformation(rd, handleFlagInherit, 0)) {
result = subprocess_error_pipe;
goto cleanup;
}
fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0);
if (-1 == fd) {
result = subprocess_error_pipe;
goto cleanup;
}
rd = SUBPROCESS_NULL;
out_process->stderr_file = _fdopen(fd, "rb");
if (SUBPROCESS_NULL == out_process->stderr_file) {
_close(fd);
goto cleanup;
}
startInfo.hStdError = wr;
wr = SUBPROCESS_NULL;
}
if (options & subprocess_option_enable_async) {
out_process->hEventOutput =
CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1,
SUBPROCESS_NULL);
out_process->hEventError =
CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1,
SUBPROCESS_NULL);
if (!out_process->hEventOutput || !out_process->hEventError) {
result = subprocess_error_from_windows_error(GetLastError());
if (subprocess_error_unknown == result) {
result = subprocess_error_no_memory;
}
goto cleanup;
}
} else {
out_process->hEventOutput = SUBPROCESS_NULL;
out_process->hEventError = SUBPROCESS_NULL;
}
len = 0;
for (i = 0; commandLine[i]; i++) {
len++;
if (strpbrk(commandLine[i], "\t\v ") != SUBPROCESS_NULL ||
commandLine[i][0] == SUBPROCESS_NULL)
len += 2;
for (j = 0; '\0' != commandLine[i][j]; j++) {
switch (commandLine[i][j]) {
default:
break;
case '\\':
if (commandLine[i][j + 1] == '"') {
len++;
}
break;
case '"':
len++;
break;
}
len++;
}
}
commandLineCombined = SUBPROCESS_CAST(char *, _alloca(len));
if (!commandLineCombined) {
result = subprocess_error_no_memory;
goto cleanup;
}
len = 0;
for (i = 0; commandLine[i]; i++) {
if (0 != i) {
commandLineCombined[len++] = ' ';
}
need_quoting = strpbrk(commandLine[i], "\t\v ") != SUBPROCESS_NULL ||
commandLine[i][0] == SUBPROCESS_NULL;
if (need_quoting) {
commandLineCombined[len++] = '"';
}
for (j = 0; '\0' != commandLine[i][j]; j++) {
switch (commandLine[i][j]) {
default:
break;
case '\\':
if (commandLine[i][j + 1] == '"') {
commandLineCombined[len++] = '\\';
}
break;
case '"':
commandLineCombined[len++] = '\\';
break;
}
commandLineCombined[len++] = commandLine[i][j];
}
if (need_quoting) {
commandLineCombined[len++] = '"';
}
}
commandLineCombined[len] = '\0';
wide_len = MultiByteToWideChar(codePageUtf8, mbErrInvalidChars,
commandLineCombined, -1, SUBPROCESS_NULL, 0);
if (0 == wide_len) {
result = subprocess_error_from_windows_error(GetLastError());
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
commandLineCombinedWide = SUBPROCESS_CAST(
subprocess_wchar_t *,
_alloca(SUBPROCESS_CAST(subprocess_size_t, wide_len) *
sizeof(subprocess_wchar_t)));
if (!commandLineCombinedWide) {
result = subprocess_error_no_memory;
goto cleanup;
}
if (0 == MultiByteToWideChar(codePageUtf8, mbErrInvalidChars,
commandLineCombined, -1,
commandLineCombinedWide, wide_len)) {
result = subprocess_error_from_windows_error(GetLastError());
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
if (process_cwd) {
wide_len = MultiByteToWideChar(codePageUtf8, mbErrInvalidChars, process_cwd,
-1, SUBPROCESS_NULL, 0);
if (0 == wide_len) {
result = subprocess_error_from_windows_error(GetLastError());
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
process_cwd_wide = SUBPROCESS_CAST(
subprocess_wchar_t *,
_alloca(SUBPROCESS_CAST(subprocess_size_t, wide_len) *
sizeof(subprocess_wchar_t)));
if (!process_cwd_wide) {
result = subprocess_error_no_memory;
goto cleanup;
}
if (0 == MultiByteToWideChar(codePageUtf8, mbErrInvalidChars, process_cwd,
-1, process_cwd_wide, wide_len)) {
result = subprocess_error_from_windows_error(GetLastError());
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
}
if (!CreateProcessW(
SUBPROCESS_NULL,
commandLineCombinedWide, SUBPROCESS_NULL, SUBPROCESS_NULL, 1, flags, used_environment, process_cwd_wide, SUBPROCESS_PTR_CAST(LPSTARTUPINFOW,
&startInfo), SUBPROCESS_PTR_CAST(LPPROCESS_INFORMATION, &processInfo))) {
result = subprocess_error_from_windows_error(GetLastError());
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
out_process->hProcess = processInfo.hProcess;
processInfo.hProcess = SUBPROCESS_NULL;
out_process->hStdInput = startInfo.hStdInput;
startInfo.hStdInput = SUBPROCESS_NULL;
CloseHandle(processInfo.hThread);
processInfo.hThread = SUBPROCESS_NULL;
if (SUBPROCESS_NULL != startInfo.hStdOutput) {
CloseHandle(startInfo.hStdOutput);
if (startInfo.hStdError != startInfo.hStdOutput) {
CloseHandle(startInfo.hStdError);
}
startInfo.hStdOutput = SUBPROCESS_NULL;
startInfo.hStdError = SUBPROCESS_NULL;
}
out_process->alive = 1;
out_process->no_wait = async_no_wait;
return 0;
cleanup:
last_error = GetLastError();
if (subprocess_error_unknown == result) {
result = subprocess_error_from_windows_error(last_error);
}
if (out_process->stdin_file) {
fclose(out_process->stdin_file);
out_process->stdin_file = SUBPROCESS_NULL;
}
if (out_process->stdout_file) {
fclose(out_process->stdout_file);
if (out_process->stderr_file &&
(out_process->stdout_file != out_process->stderr_file)) {
fclose(out_process->stderr_file);
}
out_process->stdout_file = SUBPROCESS_NULL;
out_process->stderr_file = SUBPROCESS_NULL;
}
subprocess_close_handle(&rd);
subprocess_close_handle(&wr);
subprocess_close_handle(&startInfo.hStdInput);
if (startInfo.hStdOutput == startInfo.hStdError) {
subprocess_close_handle(&startInfo.hStdOutput);
startInfo.hStdError = SUBPROCESS_NULL;
} else {
subprocess_close_handle(&startInfo.hStdOutput);
subprocess_close_handle(&startInfo.hStdError);
}
subprocess_close_handle(&out_process->hEventOutput);
subprocess_close_handle(&out_process->hEventError);
subprocess_close_handle(&processInfo.hThread);
subprocess_close_handle(&processInfo.hProcess);
SetLastError(last_error);
return result;
#else
int stdinfd[2] = {-1, -1};
int stdoutfd[2] = {-1, -1};
int stderrfd[2] = {-1, -1};
int fd, fd_flags;
int async_no_wait;
int actions_created = 0;
int result = subprocess_error_unknown;
int saved_errno = 0;
int posix_error;
pid_t child = 0;
extern char **environ;
char *const empty_environment[1] = {SUBPROCESS_NULL};
posix_spawn_file_actions_t actions;
char *const *used_environment;
async_no_wait = subprocess_option_enable_async_no_wait ==
(options & subprocess_option_enable_async_no_wait);
if (async_no_wait && (subprocess_option_enable_async !=
(options & subprocess_option_enable_async))) {
errno = EINVAL;
return subprocess_error_invalid_options;
}
if (subprocess_option_inherit_environment ==
(options & subprocess_option_inherit_environment)) {
if (SUBPROCESS_NULL != environment) {
errno = EINVAL;
return subprocess_error_invalid_environment;
}
}
memset(out_process, 0, sizeof(*out_process));
if (0 != pipe(stdinfd)) {
saved_errno = errno;
result = subprocess_error_pipe;
goto cleanup;
}
if (0 != pipe(stdoutfd)) {
saved_errno = errno;
result = subprocess_error_pipe;
goto cleanup;
}
if (subprocess_option_combined_stdout_stderr !=
(options & subprocess_option_combined_stdout_stderr)) {
if (0 != pipe(stderrfd)) {
saved_errno = errno;
result = subprocess_error_pipe;
goto cleanup;
}
}
if (environment) {
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wcast-qual"
#pragma clang diagnostic ignored "-Wold-style-cast"
#endif
used_environment = SUBPROCESS_CONST_CAST(char *const *, environment);
#ifdef __clang__
#pragma clang diagnostic pop
#endif
} else if (subprocess_option_inherit_environment ==
(options & subprocess_option_inherit_environment)) {
used_environment = environ;
} else {
used_environment = empty_environment;
}
posix_error = posix_spawn_file_actions_init(&actions);
if (0 != posix_error) {
saved_errno = posix_error;
result = subprocess_error_from_errno(posix_error);
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
actions_created = 1;
if (process_cwd) {
#if defined(__APPLE__) && MAC_OS_X_VERSION_MIN_REQUIRED >= 260000
posix_error = posix_spawn_file_actions_addchdir(&actions, process_cwd);
#else
#if defined(__APPLE__) && defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif
posix_error = posix_spawn_file_actions_addchdir_np(&actions, process_cwd);
#if defined(__APPLE__) && defined(__clang__)
#pragma clang diagnostic pop
#endif
#endif
if (0 != posix_error) {
saved_errno = posix_error;
result = subprocess_error_from_errno(posix_error);
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
}
posix_error = posix_spawn_file_actions_addclose(&actions, stdinfd[1]);
if (0 != posix_error) {
saved_errno = posix_error;
result = subprocess_error_from_errno(posix_error);
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
posix_error =
posix_spawn_file_actions_adddup2(&actions, stdinfd[0], STDIN_FILENO);
if (0 != posix_error) {
saved_errno = posix_error;
result = subprocess_error_from_errno(posix_error);
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
posix_error = posix_spawn_file_actions_addclose(&actions, stdoutfd[0]);
if (0 != posix_error) {
saved_errno = posix_error;
result = subprocess_error_from_errno(posix_error);
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
posix_error =
posix_spawn_file_actions_adddup2(&actions, stdoutfd[1], STDOUT_FILENO);
if (0 != posix_error) {
saved_errno = posix_error;
result = subprocess_error_from_errno(posix_error);
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
if (subprocess_option_combined_stdout_stderr ==
(options & subprocess_option_combined_stdout_stderr)) {
posix_error = posix_spawn_file_actions_adddup2(&actions, STDOUT_FILENO,
STDERR_FILENO);
if (0 != posix_error) {
saved_errno = posix_error;
result = subprocess_error_from_errno(posix_error);
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
} else {
posix_error = posix_spawn_file_actions_addclose(&actions, stderrfd[0]);
if (0 != posix_error) {
saved_errno = posix_error;
result = subprocess_error_from_errno(posix_error);
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
posix_error = posix_spawn_file_actions_adddup2(&actions, stderrfd[1],
STDERR_FILENO);
if (0 != posix_error) {
saved_errno = posix_error;
result = subprocess_error_from_errno(posix_error);
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
}
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wcast-qual"
#pragma clang diagnostic ignored "-Wold-style-cast"
#endif
if (subprocess_option_search_user_path ==
(options & subprocess_option_search_user_path)) {
posix_error = posix_spawnp(&child, commandLine[0], &actions,
SUBPROCESS_NULL,
SUBPROCESS_CONST_CAST(char *const *, commandLine),
used_environment);
if (0 != posix_error) {
saved_errno = posix_error;
result = subprocess_error_from_errno(posix_error);
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
} else {
posix_error = posix_spawn(&child, commandLine[0], &actions,
SUBPROCESS_NULL,
SUBPROCESS_CONST_CAST(char *const *, commandLine),
used_environment);
if (0 != posix_error) {
saved_errno = posix_error;
result = subprocess_error_from_errno(posix_error);
if (subprocess_error_unknown == result) {
result = subprocess_error_spawn;
}
goto cleanup;
}
}
#ifdef __clang__
#pragma clang diagnostic pop
#endif
close(stdinfd[0]);
stdinfd[0] = -1;
out_process->stdin_file = fdopen(stdinfd[1], "wb");
if (SUBPROCESS_NULL == out_process->stdin_file) {
saved_errno = errno;
result = subprocess_error_from_errno(saved_errno);
goto cleanup;
}
stdinfd[1] = -1;
close(stdoutfd[1]);
stdoutfd[1] = -1;
out_process->stdout_file = fdopen(stdoutfd[0], "rb");
if (SUBPROCESS_NULL == out_process->stdout_file) {
saved_errno = errno;
result = subprocess_error_from_errno(saved_errno);
goto cleanup;
}
stdoutfd[0] = -1;
if (async_no_wait) {
fd = fileno(out_process->stdout_file);
fd_flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK);
}
if (subprocess_option_combined_stdout_stderr ==
(options & subprocess_option_combined_stdout_stderr)) {
out_process->stderr_file = out_process->stdout_file;
} else {
close(stderrfd[1]);
stderrfd[1] = -1;
out_process->stderr_file = fdopen(stderrfd[0], "rb");
if (SUBPROCESS_NULL == out_process->stderr_file) {
saved_errno = errno;
result = subprocess_error_from_errno(saved_errno);
goto cleanup;
}
stderrfd[0] = -1;
if (async_no_wait) {
fd = fileno(out_process->stderr_file);
fd_flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK);
}
}
out_process->child = child;
child = 0;
out_process->alive = 1;
out_process->no_wait = async_no_wait;
result = 0;
cleanup:
if ((0 != result) && (0 == saved_errno)) {
saved_errno = errno;
}
if ((subprocess_error_unknown == result) && (0 != saved_errno)) {
result = subprocess_error_from_errno(saved_errno);
}
if (actions_created) {
posix_spawn_file_actions_destroy(&actions);
}
if (0 != result) {
if (child) {
kill(child, 9);
waitpid(child, SUBPROCESS_NULL, 0);
}
if (out_process->stdin_file) {
fclose(out_process->stdin_file);
out_process->stdin_file = SUBPROCESS_NULL;
}
if (out_process->stdout_file) {
fclose(out_process->stdout_file);
if (out_process->stderr_file &&
(out_process->stdout_file != out_process->stderr_file)) {
fclose(out_process->stderr_file);
}
out_process->stdout_file = SUBPROCESS_NULL;
out_process->stderr_file = SUBPROCESS_NULL;
}
}
if (-1 != stdinfd[0]) {
close(stdinfd[0]);
}
if (-1 != stdinfd[1]) {
close(stdinfd[1]);
}
if (-1 != stdoutfd[0]) {
close(stdoutfd[0]);
}
if (-1 != stdoutfd[1]) {
close(stdoutfd[1]);
}
if (-1 != stderrfd[0]) {
close(stderrfd[0]);
}
if (-1 != stderrfd[1]) {
close(stderrfd[1]);
}
if ((0 != result) && (0 != saved_errno)) {
errno = saved_errno;
}
return result;
#endif
}
FILE *subprocess_stdin(const struct subprocess_s *const process) {
return process->stdin_file;
}
FILE *subprocess_stdout(const struct subprocess_s *const process) {
return process->stdout_file;
}
FILE *subprocess_stderr(const struct subprocess_s *const process) {
if (process->stdout_file != process->stderr_file) {
return process->stderr_file;
} else {
return SUBPROCESS_NULL;
}
}
int subprocess_join(struct subprocess_s *const process,
int *const out_return_code) {
#if defined(_WIN32)
const unsigned long infinite = 0xFFFFFFFF;
if (process->stdin_file) {
fclose(process->stdin_file);
process->stdin_file = SUBPROCESS_NULL;
}
if (process->hStdInput) {
CloseHandle(process->hStdInput);
process->hStdInput = SUBPROCESS_NULL;
}
WaitForSingleObject(process->hProcess, infinite);
if (out_return_code) {
if (!GetExitCodeProcess(
process->hProcess,
SUBPROCESS_PTR_CAST(unsigned long *, out_return_code))) {
return -1;
}
}
process->alive = 0;
return 0;
#else
int status;
if (process->stdin_file) {
fclose(process->stdin_file);
process->stdin_file = SUBPROCESS_NULL;
}
if (process->child) {
if (process->child != waitpid(process->child, &status, 0)) {
return -1;
}
process->child = 0;
if (WIFEXITED(status)) {
process->return_status = WEXITSTATUS(status);
} else {
process->return_status = EXIT_FAILURE;
}
process->alive = 0;
}
if (out_return_code) {
*out_return_code = process->return_status;
}
return 0;
#endif
}
int subprocess_destroy(struct subprocess_s *const process) {
if (process->stdin_file) {
fclose(process->stdin_file);
process->stdin_file = SUBPROCESS_NULL;
}
if (process->stdout_file) {
fclose(process->stdout_file);
if (process->stdout_file != process->stderr_file) {
fclose(process->stderr_file);
}
process->stdout_file = SUBPROCESS_NULL;
process->stderr_file = SUBPROCESS_NULL;
}
#if defined(_WIN32)
if (process->hProcess) {
CloseHandle(process->hProcess);
process->hProcess = SUBPROCESS_NULL;
if (process->hStdInput) {
CloseHandle(process->hStdInput);
}
if (process->hEventOutput) {
CloseHandle(process->hEventOutput);
}
if (process->hEventError) {
CloseHandle(process->hEventError);
}
}
#endif
return 0;
}
int subprocess_terminate(struct subprocess_s *const process) {
#if defined(_WIN32)
unsigned int killed_process_exit_code;
int success_terminate;
int windows_call_result;
killed_process_exit_code = 99;
windows_call_result =
TerminateProcess(process->hProcess, killed_process_exit_code);
success_terminate = (windows_call_result == 0) ? 1 : 0;
return success_terminate;
#else
int result;
result = kill(process->child, 9);
return result;
#endif
}
unsigned subprocess_read_stdout(struct subprocess_s *const process,
char *const buffer, unsigned size) {
#if defined(_WIN32)
void *handle;
unsigned long bytes_available = 0;
unsigned long bytes_read = 0;
struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL};
overlapped.hEvent = process->hEventOutput;
handle = SUBPROCESS_PTR_CAST(void *,
_get_osfhandle(_fileno(process->stdout_file)));
if (process->no_wait) {
if (!PeekNamedPipe(handle, SUBPROCESS_NULL, 0, SUBPROCESS_NULL,
&bytes_available, SUBPROCESS_NULL)) {
return 0;
}
if (0 == bytes_available) {
return 0;
}
}
if (!ReadFile(handle, buffer, size, &bytes_read,
SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) {
const unsigned long errorIoPending = 997;
unsigned long error = GetLastError();
if (error == errorIoPending) {
const uintptr_t statusPending = 0x00000103;
const int wait = statusPending == overlapped.Internal;
if (!GetOverlappedResult(handle,
SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped),
&bytes_read, wait)) {
const unsigned long errorHandleEOF = 38;
const unsigned long errorBrokenPipe = 109;
const unsigned long errorIoIncomplete = 996;
error = GetLastError();
if ((errorHandleEOF != error) && (errorBrokenPipe != error) &&
(errorIoIncomplete != error)) {
return 0;
}
}
}
}
return SUBPROCESS_CAST(unsigned, bytes_read);
#else
const int fd = fileno(process->stdout_file);
const ssize_t bytes_read = read(fd, buffer, size);
if (bytes_read < 0) {
return 0;
}
return SUBPROCESS_CAST(unsigned, bytes_read);
#endif
}
unsigned subprocess_read_stderr(struct subprocess_s *const process,
char *const buffer, unsigned size) {
#if defined(_WIN32)
void *handle;
unsigned long bytes_available = 0;
unsigned long bytes_read = 0;
struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL};
overlapped.hEvent = process->hEventError;
handle = SUBPROCESS_PTR_CAST(void *,
_get_osfhandle(_fileno(process->stderr_file)));
if (process->no_wait) {
if (!PeekNamedPipe(handle, SUBPROCESS_NULL, 0, SUBPROCESS_NULL,
&bytes_available, SUBPROCESS_NULL)) {
return 0;
}
if (0 == bytes_available) {
return 0;
}
}
if (!ReadFile(handle, buffer, size, &bytes_read,
SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) {
const unsigned long errorIoPending = 997;
unsigned long error = GetLastError();
if (error == errorIoPending) {
if (!GetOverlappedResult(handle,
SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped),
&bytes_read, 1)) {
const unsigned long errorIoIncomplete = 996;
const unsigned long errorHandleEOF = 38;
error = GetLastError();
if ((error != errorIoIncomplete) && (error != errorHandleEOF)) {
return 0;
}
}
}
}
return SUBPROCESS_CAST(unsigned, bytes_read);
#else
const int fd = fileno(process->stderr_file);
const ssize_t bytes_read = read(fd, buffer, size);
if (bytes_read < 0) {
return 0;
}
return SUBPROCESS_CAST(unsigned, bytes_read);
#endif
}
int subprocess_alive(struct subprocess_s *const process) {
int is_alive = SUBPROCESS_CAST(int, process->alive);
if (!is_alive) {
return 0;
}
#if defined(_WIN32)
{
const unsigned long zero = 0x0;
const unsigned long wait_object_0 = 0x00000000L;
is_alive = wait_object_0 != WaitForSingleObject(process->hProcess, zero);
}
#else
{
int status;
is_alive = 0 == waitpid(process->child, &status, WNOHANG);
if (!is_alive) {
if (WIFEXITED(status)) {
process->return_status = WEXITSTATUS(status);
} else {
process->return_status = EXIT_FAILURE;
}
process->child = 0;
if (subprocess_join(process, SUBPROCESS_NULL)) {
return -1;
}
}
}
#endif
if (!is_alive) {
process->alive = 0;
}
return is_alive;
}
#if defined(__clang__)
#if __has_warning("-Wunsafe-buffer-usage")
#pragma clang diagnostic pop
#endif
#endif
#if defined(__cplusplus)
} #endif
#endif