Skip to content

Instantly share code, notes, and snippets.

@Prince781
Last active July 19, 2020 10:44
Show Gist options
  • Save Prince781/2f9ca7906dffc64b45af947fb1011c04 to your computer and use it in GitHub Desktop.
Save Prince781/2f9ca7906dffc64b45af947fb1011c04 to your computer and use it in GitHub Desktop.
rewriting g_spawn_sync()'s Win32 backend...
#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