// Search for program names in the PATH variable. Always enabled on Windows.
// Note: this will **not** search for paths in any provided custom environment
// and instead uses the PATH of the spawning process.
- subprocess_option_search_user_path = 0x10
+ subprocess_option_search_user_path = 0x10,
+
+ // Make subprocess_read_stdout and subprocess_read_stderr return immediately
+ // with 0 if no data is available. Requires subprocess_option_enable_async.
+ subprocess_option_enable_async_no_wait = 0x20
+};
+
+// Error codes returned by subprocess_create and subprocess_create_ex.
+// subprocess_error_success is always zero; all errors are non-zero.
+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)
/// @brief Create a process.
/// @param command_line An array of strings for the command line to execute for
/// this process. The last element must be NULL to signify the end of the array.
-/// The memory backing this parameter only needs to persist until this function
-/// returns.
+/// On Windows these strings are interpreted as UTF-8 and passed to the Unicode
+/// process creation APIs. The memory backing this parameter only needs to
+/// persist until this function returns.
/// @param options A bit field of subprocess_option_e's to pass.
/// @param out_process The newly created process.
-/// @return On success zero is returned.
+/// @return On success zero is returned. On failure a non-zero
+/// `subprocess_error_e` value is returned; inspect `errno` on POSIX platforms
+/// or `GetLastError()` on Windows for the platform-specific failure reason.
subprocess_weak int subprocess_create(const char *const command_line[],
int options,
struct subprocess_s *const out_process);
/// @brief Create a process (extended create).
/// @param command_line An array of strings for the command line to execute for
/// this process. The last element must be NULL to signify the end of the array.
-/// The memory backing this parameter only needs to persist until this function
-/// returns.
+/// On Windows these strings are interpreted as UTF-8 and passed to the Unicode
+/// process creation APIs. The memory backing this parameter only needs to
+/// persist until this function returns.
/// @param options A bit field of subprocess_option_e's to pass.
/// @param environment An optional array of strings for the environment to use
/// for a child process (each element of the form FOO=BAR). The last element
-/// must be NULL to signify the end of the array.
+/// must be NULL to signify the end of the array. On Windows these strings are
+/// interpreted as UTF-8.
+/// @param process_cwd The current working directory of the newly created
+/// process. If NULL, will be the same as the parent process. On Windows this
+/// string is interpreted as UTF-8.
/// @param out_process The newly created process.
-/// @return On success zero is returned.
+/// @return On success zero is returned. On failure a non-zero
+/// `subprocess_error_e` value is returned; inspect `errno` on POSIX platforms
+/// or `GetLastError()` on Windows for the platform-specific failure reason.
///
/// If `options` contains `subprocess_option_inherit_environment`, then
/// `environment` must be NULL.
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);
/// @brief Get the standard input file for a process.
/// @param buffer The buffer to read into.
/// @param size The maximum number of bytes to read.
/// @return The number of bytes actually read into buffer. Can only be 0 if the
-/// process has complete.
+/// process has complete, or if the process was created with
+/// `subprocess_option_enable_async_no_wait` and no data is currently available.
///
/// The only safe way to read from the standard output of a process during it's
/// execution is to use the `subprocess_option_enable_async` option in
/// @param buffer The buffer to read into.
/// @param size The maximum number of bytes to read.
/// @return The number of bytes actually read into buffer. Can only be 0 if the
-/// process has complete.
+/// process has complete, or if the process was created with
+/// `subprocess_option_enable_async_no_wait` and no data is currently available.
///
/// The only safe way to read from the standard error of a process during it's
/// execution is to use the `subprocess_option_enable_async` option in
#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>
#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 struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION;
typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES;
-typedef struct _STARTUPINFOA *LPSTARTUPINFOA;
+typedef struct _STARTUPINFOW *LPSTARTUPINFOW;
typedef struct _OVERLAPPED *LPOVERLAPPED;
#ifdef __clang__
#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;
struct subprocess_startup_info_s {
unsigned long cb;
- char *lpReserved;
- char *lpDesktop;
- char *lpTitle;
+ subprocess_wchar_t *lpReserved;
+ subprocess_wchar_t *lpDesktop;
+ subprocess_wchar_t *lpTitle;
unsigned long dwX;
unsigned long dwY;
unsigned long dwXSize;
void *hEvent;
};
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
#ifdef __MINGW32__
#pragma GCC diagnostic pop
#endif
#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 **,
void *);
__declspec(dllimport) void *__stdcall CreateEventA(LPSECURITY_ATTRIBUTES, int,
int, const char *);
-__declspec(dllimport) int __stdcall CreateProcessA(
- const char *, char *, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, int,
- unsigned long, void *, const char *, LPSTARTUPINFOA, LPPROCESS_INFORMATION);
+__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);
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)
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);
int return_status;
#endif
- subprocess_size_t alive;
+ 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
#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;
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)
openExisting, fileAttributeNormal, SUBPROCESS_NULL);
if (invalidHandleValue == *wr) {
+ subprocess_close_handle(rd);
return -1;
}
int subprocess_create(const char *const commandLine[], int options,
struct subprocess_s *const out_process) {
return subprocess_create_ex(commandLine, options, SUBPROCESS_NULL,
- out_process);
+ 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;
- void *rd, *wr;
+ 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;
- struct subprocess_subprocess_information_s processInfo;
+ 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};
- char *used_environment = SUBPROCESS_NULL;
+ 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,
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;
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 = SUBPROCESS_CONST_CAST(char *, "\0\0");
+ used_environment = empty_environment;
} else {
- // We always end with two null terminators.
- len = 2;
+ // We always end with two null terminators. MultiByteToWideChar includes
+ // each environment string's null terminator, so start with one extra.
+ len = 1;
for (i = 0; environment[i]; i++) {
- for (j = 0; '\0' != environment[i][j]; j++) {
- len++;
+ 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;
}
- // For the null terminator too.
- len++;
+ len += SUBPROCESS_CAST(subprocess_size_t, wide_len);
}
- used_environment = SUBPROCESS_CAST(char *, _alloca(len));
+ if (((SUBPROCESS_CAST(subprocess_size_t, -1)) /
+ sizeof(subprocess_wchar_t)) < len) {
+ result = subprocess_error_no_memory;
+ goto cleanup;
+ }
- // Re-use len for the insertion position
+ used_environment = SUBPROCESS_CAST(
+ subprocess_wchar_t *, _alloca(len * sizeof(subprocess_wchar_t)));
+ if (!used_environment) {
+ result = subprocess_error_no_memory;
+ goto cleanup;
+ }
+
+ // Re-use len for the insertion position.
len = 0;
for (i = 0; environment[i]; i++) {
- for (j = 0; '\0' != environment[i][j]; j++) {
- used_environment[len++] = environment[i][j];
+ 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;
}
- used_environment[len++] = '\0';
+ len += SUBPROCESS_CAST(subprocess_size_t, wide_len);
}
- // End with the two null terminators.
- used_environment[len++] = '\0';
- used_environment[len++] = '\0';
+ // End with the second null terminator.
+ used_environment[len++] = 0;
}
} else {
if (SUBPROCESS_NULL != environment) {
- return -1;
+ return subprocess_error_invalid_environment;
}
}
if (!CreatePipe(&rd, &wr, SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr),
0)) {
- return -1;
+ result = subprocess_error_pipe;
+ goto cleanup;
}
if (!SetHandleInformation(wr, handleFlagInherit, 0)) {
- return -1;
+ 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;
- if (-1 != fd) {
- out_process->stdin_file = _fdopen(fd, "wb");
-
- if (SUBPROCESS_NULL == out_process->stdin_file) {
- return -1;
- }
+ 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)) {
- return -1;
+ result = subprocess_error_pipe;
+ goto cleanup;
}
} else {
if (!CreatePipe(&rd, &wr,
SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) {
- return -1;
+ result = subprocess_error_pipe;
+ goto cleanup;
}
}
if (!SetHandleInformation(rd, handleFlagInherit, 0)) {
- return -1;
+ 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;
- if (-1 != fd) {
- out_process->stdout_file = _fdopen(fd, "rb");
-
- if (SUBPROCESS_NULL == out_process->stdout_file) {
- return -1;
- }
+ 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)) {
} else {
if (options & subprocess_option_enable_async) {
if (subprocess_create_named_pipe_helper(&rd, &wr)) {
- return -1;
+ result = subprocess_error_pipe;
+ goto cleanup;
}
} else {
if (!CreatePipe(&rd, &wr,
SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) {
- return -1;
+ result = subprocess_error_pipe;
+ goto cleanup;
}
}
if (!SetHandleInformation(rd, handleFlagInherit, 0)) {
- return -1;
+ 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;
- if (-1 != fd) {
- out_process->stderr_file = _fdopen(fd, "rb");
-
- if (SUBPROCESS_NULL == out_process->stderr_file) {
- return -1;
- }
+ 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->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;
commandLineCombined = SUBPROCESS_CAST(char *, _alloca(len));
if (!commandLineCombined) {
- return -1;
+ result = subprocess_error_no_memory;
+ goto cleanup;
}
// Gonna re-use len to store the write index into commandLineCombined
commandLineCombined[len] = '\0';
- if (!CreateProcessA(
+ 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,
- commandLineCombined, // command line
- SUBPROCESS_NULL, // process security attributes
- SUBPROCESS_NULL, // primary thread security attributes
- 1, // handles are inherited
- flags, // creation flags
- used_environment, // used environment
- SUBPROCESS_NULL, // use parent's current directory
- SUBPROCESS_PTR_CAST(LPSTARTUPINFOA,
+ commandLineCombinedWide, // command line
+ SUBPROCESS_NULL, // process security attributes
+ SUBPROCESS_NULL, // primary thread security attributes
+ 1, // handles are inherited
+ flags, // creation flags
+ used_environment, // used environment
+ process_cwd_wide, // use specified current directory
+ SUBPROCESS_PTR_CAST(LPSTARTUPINFOW,
&startInfo), // STARTUPINFO pointer
SUBPROCESS_PTR_CAST(LPPROCESS_INFORMATION, &processInfo))) {
- return -1;
+ 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;
// We don't need the handle of the primary thread in the called process.
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];
- int stdoutfd[2];
- int stderrfd[2];
- pid_t child;
+ 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) {
- return -1;
+ errno = EINVAL;
+ return subprocess_error_invalid_environment;
}
}
+ memset(out_process, 0, sizeof(*out_process));
+
if (0 != pipe(stdinfd)) {
- return -1;
+ saved_errno = errno;
+ result = subprocess_error_pipe;
+ goto cleanup;
}
if (0 != pipe(stdoutfd)) {
- return -1;
+ 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)) {
- return -1;
+ saved_errno = errno;
+ result = subprocess_error_pipe;
+ goto cleanup;
}
}
used_environment = empty_environment;
}
- if (0 != posix_spawn_file_actions_init(&actions)) {
- return -1;
+ 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;
+
+ // Set working directory
+ 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;
+ }
}
// Close the stdin write end
- if (0 != posix_spawn_file_actions_addclose(&actions, stdinfd[1])) {
- posix_spawn_file_actions_destroy(&actions);
- return -1;
+ 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;
}
// Map the read end to stdin
- if (0 !=
- posix_spawn_file_actions_adddup2(&actions, stdinfd[0], STDIN_FILENO)) {
- posix_spawn_file_actions_destroy(&actions);
- return -1;
+ 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;
}
// Close the stdout read end
- if (0 != posix_spawn_file_actions_addclose(&actions, stdoutfd[0])) {
- posix_spawn_file_actions_destroy(&actions);
- return -1;
+ 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;
}
// Map the write end to stdout
- if (0 !=
- posix_spawn_file_actions_adddup2(&actions, stdoutfd[1], STDOUT_FILENO)) {
- posix_spawn_file_actions_destroy(&actions);
- return -1;
+ 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)) {
- if (0 != posix_spawn_file_actions_adddup2(&actions, STDOUT_FILENO,
- STDERR_FILENO)) {
- posix_spawn_file_actions_destroy(&actions);
- return -1;
+ 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 {
// Close the stderr read end
- if (0 != posix_spawn_file_actions_addclose(&actions, stderrfd[0])) {
- posix_spawn_file_actions_destroy(&actions);
- return -1;
+ 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;
}
// Map the write end to stdout
- if (0 != posix_spawn_file_actions_adddup2(&actions, stderrfd[1],
- STDERR_FILENO)) {
- posix_spawn_file_actions_destroy(&actions);
- return -1;
+ 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;
}
}
#endif
if (subprocess_option_search_user_path ==
(options & subprocess_option_search_user_path)) {
- if (0 != posix_spawnp(&child, commandLine[0], &actions, SUBPROCESS_NULL,
- SUBPROCESS_CONST_CAST(char *const *, commandLine),
- used_environment)) {
- posix_spawn_file_actions_destroy(&actions);
- return -1;
+ 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 {
- if (0 != posix_spawn(&child, commandLine[0], &actions, SUBPROCESS_NULL,
- SUBPROCESS_CONST_CAST(char *const *, commandLine),
- used_environment)) {
- posix_spawn_file_actions_destroy(&actions);
- return -1;
+ 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__
// Close the stdin read end
close(stdinfd[0]);
+ stdinfd[0] = -1;
// Store the stdin write end
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 the stdout write end
close(stdoutfd[1]);
+ stdoutfd[1] = -1;
// Store the stdout read end
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;
+
+ // Set non blocking if we are async and asked not to wait.
+ 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)) {
} else {
// Close the stderr write end
close(stderrfd[1]);
+ stderrfd[1] = -1;
// Store the stderr read end
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;
+
+ // Set non blocking if we are async and asked not to wait.
+ 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);
+ }
}
// Store the child's pid
out_process->child = child;
+ child = 0;
out_process->alive = 1;
+ out_process->no_wait = async_no_wait;
- posix_spawn_file_actions_destroy(&actions);
- return 0;
+ 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
}
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;
// Means we've got an async read!
if (error == errorIoPending) {
+ const uintptr_t statusPending = 0x00000103;
+
+ const int wait = statusPending == overlapped.Internal;
+
if (!GetOverlappedResult(handle,
SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped),
- &bytes_read, 1)) {
- const unsigned long errorIoIncomplete = 996;
+ &bytes_read, wait)) {
const unsigned long errorHandleEOF = 38;
+ const unsigned long errorBrokenPipe = 109;
+ const unsigned long errorIoIncomplete = 996;
error = GetLastError();
- if ((error != errorIoIncomplete) && (error != errorHandleEOF)) {
+ if ((errorHandleEOF != error) && (errorBrokenPipe != error) &&
+ (errorIoIncomplete != error)) {
return 0;
}
}
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;