From 0cea36222fe9bac5ebfc45716c9eef11f37046c4 Mon Sep 17 00:00:00 2001 From: Xuan-Son Nguyen Date: Fri, 24 Jul 2026 08:02:23 +0200 Subject: [PATCH] vendor: update subprocess.h (#26061) --- scripts/sync_vendor.py | 2 +- tools/server/server-models.cpp | 2 +- vendor/sheredom/subprocess.h | 829 ++++++++++++++++++++++++++++----- 3 files changed, 706 insertions(+), 127 deletions(-) diff --git a/scripts/sync_vendor.py b/scripts/sync_vendor.py index 7bab415a8..a9343e37c 100755 --- a/scripts/sync_vendor.py +++ b/scripts/sync_vendor.py @@ -21,7 +21,7 @@ vendor = { f"https://raw.githubusercontent.com/yhirose/cpp-httplib/{HTTPLIB_VERSION}/split.py": "split.py", f"https://raw.githubusercontent.com/yhirose/cpp-httplib/{HTTPLIB_VERSION}/LICENSE": "vendor/cpp-httplib/LICENSE", - "https://raw.githubusercontent.com/sheredom/subprocess.h/b49c56e9fe214488493021017bf3954b91c7c1f5/subprocess.h": "vendor/sheredom/subprocess.h", + "https://raw.githubusercontent.com/sheredom/subprocess.h/8671cee1fc09f11a70ce3782a0ee13177c3aa387/subprocess.h": "vendor/sheredom/subprocess.h", } for url, filename in vendor.items(): diff --git a/tools/server/server-models.cpp b/tools/server/server-models.cpp index 7c6bfda0f..9eac58e9d 100644 --- a/tools/server/server-models.cpp +++ b/tools/server/server-models.cpp @@ -852,7 +852,7 @@ void server_models::load(const std::string & name, const load_options & opts) { // so that we can use stdout for commands and stderr for logging int options = subprocess_option_no_window | subprocess_option_combined_stdout_stderr; inst.subproc->sproc.emplace(); - int result = subprocess_create_ex(argv.data(), options, envp.data(), &inst.subproc->get()); + int result = subprocess_create_ex(argv.data(), options, envp.data(), nullptr, &inst.subproc->get()); if (result != 0) { throw std::runtime_error("failed to spawn server instance"); } diff --git a/vendor/sheredom/subprocess.h b/vendor/sheredom/subprocess.h index 3e40bae04..5e809023a 100644 --- a/vendor/sheredom/subprocess.h +++ b/vendor/sheredom/subprocess.h @@ -89,7 +89,25 @@ enum subprocess_option_e { // 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) @@ -99,11 +117,14 @@ extern "C" { /// @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); @@ -111,20 +132,28 @@ subprocess_weak int subprocess_create(const char *const command_line[], /// @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. @@ -189,7 +218,8 @@ subprocess_weak int subprocess_terminate(struct subprocess_s *const 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 @@ -203,7 +233,8 @@ subprocess_read_stdout(struct subprocess_s *const process, char *const buffer, /// @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 @@ -230,9 +261,14 @@ subprocess_weak int subprocess_alive(struct subprocess_s *const process); #endif #if !defined(_WIN32) +#include +#include #include #include #include +#if defined(__APPLE__) +#include +#endif #include #include #include @@ -240,6 +276,23 @@ subprocess_weak int subprocess_alive(struct subprocess_s *const process); #if defined(_WIN32) +#include + +#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; @@ -262,7 +315,7 @@ typedef size_t subprocess_size_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__ @@ -276,6 +329,10 @@ typedef struct _OVERLAPPED *LPOVERLAPPED; #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; @@ -292,9 +349,9 @@ struct subprocess_security_attributes_s { 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; @@ -325,6 +382,9 @@ struct subprocess_overlapped_s { void *hEvent; }; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif #ifdef __MINGW32__ #pragma GCC diagnostic pop #endif @@ -333,6 +393,7 @@ struct subprocess_overlapped_s { #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 **, @@ -352,9 +413,12 @@ __declspec(dllimport) void *__stdcall CreateFileA(const char *, unsigned long, 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); @@ -365,6 +429,10 @@ __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) @@ -380,6 +448,7 @@ __declspec(dllimport) int __stdcall GetOverlappedResult(void *, LPOVERLAPPED, 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); @@ -414,12 +483,68 @@ struct subprocess_s { 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 @@ -429,6 +554,18 @@ struct subprocess_s { #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; @@ -442,9 +579,23 @@ int subprocess_create_named_pipe_helper(void **rd, void **wr) { 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) @@ -472,6 +623,7 @@ int subprocess_create_named_pipe_helper(void **rd, void **wr) { openExisting, fileAttributeNormal, SUBPROCESS_NULL); if (invalidHandleValue == *wr) { + subprocess_close_handle(rd); return -1; } @@ -482,27 +634,41 @@ int subprocess_create_named_pipe_helper(void **rd, void **wr) { 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, @@ -522,6 +688,14 @@ int subprocess_create_ex(const char *const commandLine[], int options, 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; @@ -529,93 +703,142 @@ int subprocess_create_ex(const char *const commandLine[], int options, 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; } - used_environment[len++] = '\0'; + 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); } - // 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)) { @@ -624,30 +847,37 @@ int subprocess_create_ex(const char *const commandLine[], int options, } 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) { @@ -657,6 +887,13 @@ int subprocess_create_ex(const char *const commandLine[], int options, 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; @@ -694,7 +931,8 @@ int subprocess_create_ex(const char *const commandLine[], int options, 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 @@ -735,27 +973,93 @@ int subprocess_create_ex(const char *const commandLine[], int options, 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); @@ -763,40 +1067,111 @@ int subprocess_create_ex(const char *const commandLine[], int options, 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; } } @@ -817,54 +1192,120 @@ int subprocess_create_ex(const char *const commandLine[], int options, 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; } } @@ -875,18 +1316,30 @@ int subprocess_create_ex(const char *const commandLine[], int options, #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__ @@ -895,13 +1348,34 @@ int subprocess_create_ex(const char *const commandLine[], int options, // 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)) { @@ -909,17 +1383,92 @@ int subprocess_create_ex(const char *const commandLine[], int options, } 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 } @@ -1060,6 +1609,7 @@ 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; @@ -1067,6 +1617,17 @@ unsigned subprocess_read_stdout(struct subprocess_s *const process, 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; @@ -1074,14 +1635,20 @@ unsigned subprocess_read_stdout(struct subprocess_s *const process, // 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; } } @@ -1105,6 +1672,7 @@ 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; @@ -1112,6 +1680,17 @@ unsigned subprocess_read_stderr(struct subprocess_s *const process, 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;