Created
February 1, 2018 02:00
-
-
Save leok7v/2dd5179d06c6ced3b6a818fc39439658 to your computer and use it in GitHub Desktop.
Windows: system_np() - Creating a Child Process with Redirected Input and Output
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <windows.h> | |
#include <stdio.h> | |
#include <malloc.h> | |
// Reworked MSDN sample starting with https://stackoverflow.com/questions/14147138/capture-output-of-spawned-process-to-string | |
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx | |
// Implemented in ~C99 instead of C++ | |
// Child Process window hidden (for use from WinMain() /SYSTEM:WINDOWS executable) | |
// Creates background thread for peeking the pipes to avoid deadlocks. | |
// Implements timeout in milliseconds and tames CPU utilization with WaitForMultipleObjects. | |
// Does not allocate memory (no malloc() free() calls) | |
#ifdef __cplusplus | |
#define BEGIN_C extern "C" { | |
#define END_C } // extern "C" | |
#define null nullptr | |
#else | |
#define BEGIN_C | |
#define END_C | |
#define null ((void*)0) | |
#endif | |
BEGIN_C | |
int system_np(const char* command, int timeout_milliseconds, | |
char* stdout_data, int stdout_data_size, | |
char* stderr_data, int stderr_data_size, int* exit_code); | |
typedef struct system_np_s { | |
HANDLE child_stdout_read; | |
HANDLE child_stderr_read; | |
HANDLE reader; | |
PROCESS_INFORMATION pi; | |
const char* command; | |
char* stdout_data; | |
int stdout_data_size; | |
char* stderr_data; | |
int stderr_data_size; | |
int* exit_code; | |
int timeout; // timeout in milliseconds or -1 for INIFINTE | |
} system_np_t; | |
static char data_stdout[16 * 1024 * 1024]; | |
static char data_stderr[16 * 1024 * 1024]; | |
int main(int argc, char *argv[]) { | |
int bytes = 1; | |
for (int i = 1; i < argc; i++) { | |
bytes += (int)strlen(argv[i]) + 1; | |
} | |
char* command = (char*)alloca(bytes); | |
command[0] = 0; | |
char* p = command; | |
for (int i = 1; i < argc; i++) { | |
int n = (int)strlen(argv[i]); | |
memcpy(p, argv[i], n); p += n; | |
*p = (i == argc - 1) ? 0x00 : 0x20; | |
p++; | |
} | |
int exit_code = 0; | |
if (command[0] == 0) { | |
command = (char*)"cmd.exe /c \"dir /w /b\""; | |
} | |
int r = system_np(command, 100 * 1000, data_stdout, sizeof(data_stdout), data_stderr, sizeof(data_stderr), &exit_code); | |
if (r != 0) { | |
fprintf(stderr, "system_np failed: %d 0x%08x %s", r, r, strerror(r)); | |
return r; | |
} else { | |
fwrite(data_stdout, strlen(data_stdout), 1, stdout); | |
fwrite(data_stderr, strlen(data_stderr), 1, stderr); | |
return exit_code; | |
} | |
} | |
static int peek_pipe(HANDLE pipe, char* data, int size) { | |
char buffer[4 * 1024]; | |
DWORD read = 0; | |
DWORD available = 0; | |
bool b = PeekNamedPipe(pipe, null, sizeof(data), null, &available, null); | |
if (!b) { | |
return -1; | |
} else if (available > 0) { | |
int bytes = min(sizeof(buffer), available); | |
b = ReadFile(pipe, buffer, bytes, &read, null); | |
if (!b) { | |
return -1; | |
} | |
if (data != null && size > 0) { | |
int n = min(size - 1, (int)read); | |
memcpy(data, buffer, n); | |
data[n + 1] = 0; // always zero terminated | |
return n; | |
} | |
} | |
return 0; | |
} | |
static DWORD WINAPI read_from_all_pipes_fully(void* p) { | |
system_np_t* system = (system_np_t*)p; | |
unsigned long long milliseconds = GetTickCount64(); // since boot time | |
char* out = system->stdout_data != null && system->stdout_data_size > 0 ? system->stdout_data : null; | |
char* err = system->stderr_data != null && system->stderr_data_size > 0 ? system->stderr_data : null; | |
int out_bytes = system->stdout_data != null && system->stdout_data_size > 0 ? system->stdout_data_size - 1 : 0; | |
int err_bytes = system->stderr_data != null && system->stderr_data_size > 0 ? system->stderr_data_size - 1 : 0; | |
for (;;) { | |
int read_stdout = peek_pipe(system->child_stdout_read, out, out_bytes); | |
if (read_stdout > 0 && out != null) { out += read_stdout; out_bytes -= read_stdout; } | |
int read_stderr = peek_pipe(system->child_stderr_read, err, err_bytes); | |
if (read_stderr > 0 && err != null) { err += read_stderr; err_bytes -= read_stderr; } | |
if (read_stdout < 0 && read_stderr < 0) { break; } // both pipes are closed | |
unsigned long long time_spent_in_milliseconds = GetTickCount64() - milliseconds; | |
if (system->timeout > 0 && time_spent_in_milliseconds > system->timeout) { break; } | |
if (read_stdout == 0 && read_stderr == 0) { // nothing has been read from both pipes | |
HANDLE handles[2] = {system->child_stdout_read, system->child_stderr_read}; | |
WaitForMultipleObjects(2, handles, false, 1); // wait for at least 1 millisecond (more likely 16) | |
} | |
} | |
if (out != null) { *out = 0; } | |
if (err != null) { *err = 0; } | |
return 0; | |
} | |
static int create_child_process(system_np_t* system) { | |
SECURITY_ATTRIBUTES sa = {0}; | |
sa.nLength = sizeof(SECURITY_ATTRIBUTES); | |
sa.bInheritHandle = true; | |
sa.lpSecurityDescriptor = null; | |
HANDLE child_stdout_write = INVALID_HANDLE_VALUE; | |
HANDLE child_stderr_write = INVALID_HANDLE_VALUE; | |
if (!CreatePipe(&system->child_stderr_read, &child_stderr_write, &sa, 0) ) { | |
return GetLastError(); | |
} | |
if (!SetHandleInformation(system->child_stderr_read, HANDLE_FLAG_INHERIT, 0) ){ | |
return GetLastError(); | |
} | |
if (!CreatePipe(&system->child_stdout_read, &child_stdout_write, &sa, 0) ) { | |
return GetLastError(); | |
} | |
if (!SetHandleInformation(system->child_stdout_read, HANDLE_FLAG_INHERIT, 0) ){ | |
return GetLastError(); | |
} | |
// Set the text I want to run | |
STARTUPINFO siStartInfo = {0}; | |
siStartInfo.cb = sizeof(STARTUPINFO); | |
siStartInfo.hStdError = child_stderr_write; | |
siStartInfo.hStdOutput = child_stdout_write; | |
siStartInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; | |
siStartInfo.wShowWindow = SW_HIDE; | |
bool b = CreateProcessA(null, | |
(char*)system->command, | |
null, // process security attributes | |
null, // primary thread security attributes | |
true, // handles are inherited | |
CREATE_NO_WINDOW, // creation flags | |
null, // use parent's environment | |
null, // use parent's current directory | |
&siStartInfo, // STARTUPINFO pointer | |
&system->pi); // receives PROCESS_INFORMATION | |
int err = GetLastError(); | |
CloseHandle(child_stderr_write); | |
CloseHandle(child_stdout_write); | |
if (!b) { | |
CloseHandle(system->child_stdout_read); system->child_stdout_read = INVALID_HANDLE_VALUE; | |
CloseHandle(system->child_stderr_read); system->child_stderr_read = INVALID_HANDLE_VALUE; | |
} | |
return b ? 0 : err; | |
} | |
int system_np(const char* command, int timeout_milliseconds, | |
char* stdout_data, int stdout_data_size, | |
char* stderr_data, int stderr_data_size, int* exit_code) { | |
system_np_t system = {0}; | |
if (exit_code != null) { *exit_code = 0; } | |
if (stdout_data != null && stdout_data_size > 0) { stdout_data[0] = 0; } | |
if (stderr_data != null && stderr_data_size > 0) { stderr_data[0] = 0; } | |
system.timeout = timeout_milliseconds > 0 ? timeout_milliseconds : -1; | |
system.command = command; | |
system.stdout_data = stdout_data; | |
system.stderr_data = stderr_data; | |
system.stdout_data_size = stdout_data_size; | |
system.stderr_data_size = stderr_data_size; | |
int r = create_child_process(&system); | |
if (r == 0) { | |
system.reader = CreateThread(null, 0, read_from_all_pipes_fully, &system, 0, null); | |
if (system.reader == null) { // in theory should rarely happen only when system super low on resources | |
r = GetLastError(); | |
TerminateProcess(system.pi.hProcess, ECANCELED); | |
} else { | |
bool thread_done = WaitForSingleObject(system.pi.hThread, timeout_milliseconds) == 0; | |
bool process_done = WaitForSingleObject(system.pi.hProcess, timeout_milliseconds) == 0; | |
if (!thread_done || !process_done) { | |
TerminateProcess(system.pi.hProcess, ETIME); | |
} | |
if (exit_code != null) { | |
GetExitCodeProcess(system.pi.hProcess, (DWORD*)exit_code); | |
} | |
CloseHandle(system.pi.hThread); | |
CloseHandle(system.pi.hProcess); | |
CloseHandle(system.child_stdout_read); system.child_stdout_read = INVALID_HANDLE_VALUE; | |
CloseHandle(system.child_stderr_read); system.child_stderr_read = INVALID_HANDLE_VALUE; | |
WaitForSingleObject(system.reader, INFINITE); // join thread | |
CloseHandle(system.reader); | |
} | |
} | |
if (stdout_data != null && stdout_data_size > 0) { stdout_data[stdout_data_size - 1] = 0; } | |
if (stderr_data != null && stderr_data_size > 0) { stderr_data[stderr_data_size - 1] = 0; } | |
return r; | |
} | |
END_C |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment