Last active
July 19, 2020 10:44
-
-
Save Prince781/2f9ca7906dffc64b45af947fb1011c04 to your computer and use it in GitHub Desktop.
rewriting g_spawn_sync()'s Win32 backend...
This file contains hidden or 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 <stdio.h> | |
#include <stdbool.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <inttypes.h> | |
#include <Windows.h> | |
#define return_val_if_fail(expr, val)\ | |
do {\ | |
if (!(expr))\ | |
return val;\ | |
} while (0); | |
/** | |
* see https://docs.microsoft.com/en-us/windows/win32/ProcThread/creating-a-child-process-with-redirected-input-and-output | |
* @param error comes from calling GetLastError() | |
* @returns a statically-allocated message. don't free it | |
*/ | |
const char* | |
win32_strerror(DWORD error) | |
{ | |
static __declspec(thread) char msgbuf[1024]; | |
FormatMessageA( | |
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, | |
NULL, | |
error, | |
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | |
(LPTSTR)&msgbuf[0], | |
(DWORD)sizeof(msgbuf), | |
NULL); | |
return msgbuf; | |
} | |
char* | |
read_file_into_string(HANDLE hFile) | |
{ | |
size_t alloc_size = 1024; // total amount allocated | |
char* buffer = malloc(alloc_size); | |
size_t buf_len = 0; // total amount read | |
size_t amt_read_once = 0; | |
char* buffer_at_offset = buffer; | |
while (ReadFile(hFile, buffer_at_offset, alloc_size - buf_len, &amt_read_once, NULL)) { | |
buf_len += amt_read_once; | |
if (buf_len >= 3*alloc_size/4) { | |
// resize | |
alloc_size *= 2; | |
char *rbuffer = realloc(buffer, alloc_size); | |
if (!rbuffer) | |
goto realloc_failure; | |
buffer = rbuffer; | |
} | |
buffer_at_offset = buffer + buf_len; | |
} | |
if (amt_read_once != 0 || buf_len == 0) | |
fprintf(stderr, "%s: ReadFile() (possibly) failed - %s", __func__, win32_strerror(GetLastError())); | |
char *rbuffer = realloc(buffer, buf_len + 1); | |
if (!rbuffer) | |
goto realloc_failure; | |
buffer = rbuffer; | |
buffer[buf_len++] = '\0'; | |
return buffer; | |
realloc_failure: | |
fprintf(stderr, "%s: realloc() failed\n", __func__); | |
free(buffer); | |
return NULL; | |
} | |
char * | |
escape_char(const char* unescaped_str, char to_escape) | |
{ | |
return_val_if_fail(unescaped_str, NULL); | |
int length = 0; | |
int to_escape_count = 0; | |
int new_length = 0; | |
char* new_escaped_str = NULL; | |
while (unescaped_str[length]) { | |
if (unescaped_str[length] == to_escape) | |
to_escape_count++; | |
length++; | |
} | |
new_escaped_str = malloc(length + to_escape_count + 1 /* trailing NUL char */); | |
for (int i = 0; i < length; i++) | |
{ | |
if (unescaped_str[i] == to_escape) | |
new_escaped_str[new_length++] = '\\'; | |
new_escaped_str[new_length++] = unescaped_str[i]; | |
} | |
new_escaped_str[new_length] = '\0'; | |
return new_escaped_str; | |
} | |
/** | |
* @param working_directory can be NULL | |
* @param argv must be non-NULL and NULL-terminated | |
* @param envp either NULL or a NULL-terminated array of strings | |
* | |
* @return %true on success, or %false on error | |
*/ | |
bool | |
fixed_spawn_sync(const char* working_directory, | |
const char** argv, | |
const char** envp, | |
char** standard_output, | |
char** standard_error, | |
int* exit_status) | |
{ | |
return_val_if_fail(argv != NULL, false); | |
return_val_if_fail(argv[0] != NULL, false); | |
// MSVC-specific thread-local keyword: https://docs.microsoft.com/en-us/cpp/parallel/thread-local-storage-tls?view=vs-2019 | |
// maximum length of command string in CreateProcess is INT16_MAX: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw | |
static __declspec(thread) char command_string[INT16_MAX]; | |
int cmdstr_len = 0; | |
// the first element is the read end, and the second is the write end of the pipe | |
HANDLE pipe_proc_stdin[2] = { 0, 0 }; | |
HANDLE pipe_proc_stdout[2] = { 0, 0 }; | |
HANDLE pipe_proc_stderr[2] = { 0, 0 }; | |
bool createproc_result = false; | |
STARTUPINFOA si = { 0 }; | |
PROCESS_INFORMATION pi = { 0 }; | |
SECURITY_ATTRIBUTES sa = { 0 }; // security attributes for pipes | |
sa.nLength = sizeof(sa); | |
sa.bInheritHandle = true; | |
// create pipes (see https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe) | |
if (!CreatePipe(&pipe_proc_stdin[0], &pipe_proc_stdin[1], &sa, 0)) | |
return false; | |
if (!CreatePipe(&pipe_proc_stdout[0], &pipe_proc_stdout[1], &sa, 0)) | |
goto cleanup; | |
if (!CreatePipe(&pipe_proc_stderr[0], &pipe_proc_stderr[1], &sa, 0)) | |
goto cleanup; | |
// ensure the read handles for the stdout/stderr pipes aren't inherited, by removing the INHERIT flag | |
// ensure write handle for stdin pipe isn't inherited | |
if (!SetHandleInformation(pipe_proc_stdin[1], HANDLE_FLAG_INHERIT, 0)) | |
goto cleanup; | |
if (!SetHandleInformation(pipe_proc_stdout[0], HANDLE_FLAG_INHERIT, 0)) | |
goto cleanup; | |
if (!SetHandleInformation(pipe_proc_stderr[0], HANDLE_FLAG_INHERIT, 0)) | |
goto cleanup; | |
// see https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa | |
si.cb = sizeof(si); // the size of this structure | |
si.dwFlags |= STARTF_USESTDHANDLES; | |
si.hStdInput = pipe_proc_stdin[0]; | |
si.hStdOutput = pipe_proc_stdout[1]; | |
si.hStdError = pipe_proc_stderr[1]; | |
char* cmdstr_at_offset = command_string; | |
cmdstr_len += snprintf(cmdstr_at_offset, sizeof(command_string) - cmdstr_len, "%s", argv[0]); | |
cmdstr_at_offset = command_string + cmdstr_len; | |
// now create the command string after argv[0] | |
for (int arg_i = 1; argv[arg_i] && cmdstr_len + strlen(argv[arg_i]) < sizeof(command_string); arg_i++) { | |
char* escaped = escape_char(argv[arg_i], '"'); | |
cmdstr_len += snprintf(cmdstr_at_offset, sizeof(command_string) - cmdstr_len, " \"%s\"", escaped); | |
cmdstr_at_offset = command_string + cmdstr_len; | |
free(escaped); | |
} | |
command_string[cmdstr_len] = '\0'; | |
printf("launching this command:\n%s\n...\n", command_string); | |
createproc_result = CreateProcessA(NULL, command_string, NULL, NULL, true, CREATE_NO_WINDOW, NULL /* TODO: parse envp */, working_directory, &si, &pi); | |
if (!createproc_result) { | |
fprintf(stderr, "%s: CreateProcessW() failed - %s\n", __func__, win32_strerror(GetLastError())); | |
goto cleanup; | |
} | |
// close handles that the parent process doesn't need | |
CloseHandle(pipe_proc_stdin[0]); | |
pipe_proc_stdin[0] = NULL; | |
CloseHandle(pipe_proc_stdout[1]); | |
pipe_proc_stdout[1] = NULL; | |
CloseHandle(pipe_proc_stderr[1]); | |
pipe_proc_stderr[1] = NULL; | |
// now, read stdout and stderr into string | |
if (standard_output) | |
*standard_output = read_file_into_string(pipe_proc_stdout[0]); | |
if (standard_error) | |
*standard_error = read_file_into_string(pipe_proc_stderr[0]); | |
// sleep until child process completes | |
while (WaitForSingleObject(pi.hProcess, INFINITE) == STILL_ACTIVE) | |
; | |
// get return code | |
if (exit_status) { | |
if (!GetExitCodeProcess(pi.hProcess, exit_status)) { | |
fprintf(stderr, "%s: GetExitCodeProcess() failed - %s\n", __func__, win32_strerror(GetLastError())); | |
goto cleanup; | |
} | |
} | |
cleanup: | |
if (pipe_proc_stdin[0]) | |
CloseHandle(pipe_proc_stdin[0]); | |
if (pipe_proc_stdin[1]) | |
CloseHandle(pipe_proc_stdin[1]); | |
if (pipe_proc_stdout[0]) | |
CloseHandle(pipe_proc_stdout[0]); | |
if (pipe_proc_stdout[1]) | |
CloseHandle(pipe_proc_stdout[1]); | |
if (pipe_proc_stderr[0]) | |
CloseHandle(pipe_proc_stderr[0]); | |
if (pipe_proc_stderr[1]) | |
CloseHandle(pipe_proc_stderr[1]); | |
if (pi.hProcess) | |
CloseHandle(pi.hProcess); | |
if (pi.hThread) | |
CloseHandle(pi.hThread); | |
return createproc_result; | |
} | |
char* readline(FILE* file, size_t *n) | |
{ | |
size_t buf_len = 0; | |
size_t alloc_size = 512; | |
char* buf = malloc(alloc_size); | |
char current_char; | |
while ((current_char = fgetc(file)) != EOF) { | |
if (current_char == '\n') | |
break; | |
if (buf_len >= alloc_size) { | |
alloc_size *= 2; | |
char* rbuf = realloc(buf, alloc_size); | |
if (!rbuf) { | |
fprintf(stderr, "%s: realloc() failed\n", __func__); | |
free(buf); | |
return NULL; | |
} | |
buf = rbuf; | |
} | |
buf[buf_len++] = current_char; | |
} | |
if (buf_len >= alloc_size) { | |
alloc_size += 1; | |
char* rbuf = realloc(buf, alloc_size); | |
if (!rbuf) { | |
fprintf(stderr, "%s: realloc() failed\n", __func__); | |
free(buf); | |
return NULL; | |
} | |
buf = rbuf; | |
buf[buf_len++] = '\0'; | |
} else { | |
buf[buf_len++] = 0; | |
char* rbuf = realloc(buf, buf_len); | |
if (rbuf) { | |
alloc_size = buf_len; | |
buf = rbuf; | |
} | |
} | |
if (n) | |
*n = buf_len; | |
return buf; | |
} | |
#define MAX_ARGS 32 | |
int main() | |
{ | |
printf("hello, world\n"); | |
do { | |
size_t n_args = 0; | |
char* argv[MAX_ARGS] = { 0 }; | |
char* child_stdout = NULL; | |
char* child_stderr = NULL; | |
int child_status = 0; | |
printf("enter program and arguments to run (hit enter for each arg; enter single ';' when done): "); | |
for (; n_args < MAX_ARGS - 1; n_args++) { | |
char* line = readline(stdin, NULL); | |
if (!line) | |
break; | |
if (strcmp(line, ";") == 0) { | |
free(line); | |
break; | |
} | |
argv[n_args] = line; | |
} | |
if (n_args == 0) { | |
printf("no arguments specified. quitting ...\n"); | |
break; | |
} | |
printf("running %s ...\n", argv[0]); | |
if (fixed_spawn_sync(NULL, argv, NULL, &child_stdout, &child_stderr, &child_status)) { | |
printf("process exited with code %d.\n", child_status); | |
printf("stdout:\n%s\n", child_stdout); | |
printf("stderr:\n%s\n", child_stderr); | |
} | |
for (size_t i = 0; i < n_args; i++) { | |
free(argv[i]); | |
argv[i] = NULL; | |
} | |
free(child_stdout); | |
free(child_stderr); | |
} while (true); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment